@sw4rm/js-sdk 0.5.0 → 0.6.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/README.md +165 -1
- package/dist/cjs/index.cjs +1573 -88
- package/dist/esm/index.js +1530 -80
- package/dist/types/clients/handoff.d.ts +44 -1
- package/dist/types/index.d.ts +5 -1
- package/dist/types/internal/errorMapping.d.ts +2 -0
- package/dist/types/llm/anthropic.d.ts +83 -0
- package/dist/types/llm/client.d.ts +107 -0
- package/dist/types/llm/factory.d.ts +69 -0
- package/dist/types/llm/groq.d.ts +79 -0
- package/dist/types/llm/index.d.ts +45 -0
- package/dist/types/llm/mock.d.ts +89 -0
- package/dist/types/llm/rateLimiter.d.ts +101 -0
- package/dist/types/runtime/cancellation.d.ts +41 -0
- package/dist/types/runtime/delegation.d.ts +20 -0
- package/dist/types/runtime/gateway.d.ts +80 -0
- package/package.json +1 -1
- package/protos/common.proto +7 -0
- package/protos/handoff.proto +42 -0
- package/protos/registry.proto +11 -0
package/dist/esm/index.js
CHANGED
|
@@ -1857,25 +1857,25 @@ var require_codegen = __commonJS({
|
|
|
1857
1857
|
var require_fetch = __commonJS({
|
|
1858
1858
|
"node_modules/@protobufjs/fetch/index.js"(exports2, module2) {
|
|
1859
1859
|
"use strict";
|
|
1860
|
-
module2.exports =
|
|
1860
|
+
module2.exports = fetch2;
|
|
1861
1861
|
var asPromise = require_aspromise();
|
|
1862
1862
|
var inquire2 = require_inquire();
|
|
1863
|
-
var
|
|
1864
|
-
function
|
|
1863
|
+
var fs5 = inquire2("fs");
|
|
1864
|
+
function fetch2(filename, options, callback) {
|
|
1865
1865
|
if (typeof options === "function") {
|
|
1866
1866
|
callback = options;
|
|
1867
1867
|
options = {};
|
|
1868
1868
|
} else if (!options)
|
|
1869
1869
|
options = {};
|
|
1870
1870
|
if (!callback)
|
|
1871
|
-
return asPromise(
|
|
1872
|
-
if (!options.xhr &&
|
|
1873
|
-
return
|
|
1874
|
-
return err && typeof XMLHttpRequest !== "undefined" ?
|
|
1871
|
+
return asPromise(fetch2, this, filename, options);
|
|
1872
|
+
if (!options.xhr && fs5 && fs5.readFile)
|
|
1873
|
+
return fs5.readFile(filename, function fetchReadFileCallback(err, contents) {
|
|
1874
|
+
return err && typeof XMLHttpRequest !== "undefined" ? fetch2.xhr(filename, options, callback) : err ? callback(err) : callback(null, options.binary ? contents : contents.toString("utf8"));
|
|
1875
1875
|
});
|
|
1876
|
-
return
|
|
1876
|
+
return fetch2.xhr(filename, options, callback);
|
|
1877
1877
|
}
|
|
1878
|
-
|
|
1878
|
+
fetch2.xhr = function fetch_xhr(filename, options, callback) {
|
|
1879
1879
|
var xhr = new XMLHttpRequest();
|
|
1880
1880
|
xhr.onreadystatechange = function fetchOnReadyStateChange() {
|
|
1881
1881
|
if (xhr.readyState !== 4)
|
|
@@ -1908,15 +1908,15 @@ var require_fetch = __commonJS({
|
|
|
1908
1908
|
var require_path = __commonJS({
|
|
1909
1909
|
"node_modules/@protobufjs/path/index.js"(exports2) {
|
|
1910
1910
|
"use strict";
|
|
1911
|
-
var
|
|
1911
|
+
var path7 = exports2;
|
|
1912
1912
|
var isAbsolute = (
|
|
1913
1913
|
/**
|
|
1914
1914
|
* Tests if the specified path is absolute.
|
|
1915
1915
|
* @param {string} path Path to test
|
|
1916
1916
|
* @returns {boolean} `true` if path is absolute
|
|
1917
1917
|
*/
|
|
1918
|
-
|
|
1919
|
-
return /^(?:\/|\w+:)/.test(
|
|
1918
|
+
path7.isAbsolute = function isAbsolute2(path8) {
|
|
1919
|
+
return /^(?:\/|\w+:)/.test(path8);
|
|
1920
1920
|
}
|
|
1921
1921
|
);
|
|
1922
1922
|
var normalize = (
|
|
@@ -1925,9 +1925,9 @@ var require_path = __commonJS({
|
|
|
1925
1925
|
* @param {string} path Path to normalize
|
|
1926
1926
|
* @returns {string} Normalized path
|
|
1927
1927
|
*/
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
var parts =
|
|
1928
|
+
path7.normalize = function normalize2(path8) {
|
|
1929
|
+
path8 = path8.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
|
|
1930
|
+
var parts = path8.split("/"), absolute = isAbsolute(path8), prefix = "";
|
|
1931
1931
|
if (absolute)
|
|
1932
1932
|
prefix = parts.shift() + "/";
|
|
1933
1933
|
for (var i = 0; i < parts.length; ) {
|
|
@@ -1946,7 +1946,7 @@ var require_path = __commonJS({
|
|
|
1946
1946
|
return prefix + parts.join("/");
|
|
1947
1947
|
}
|
|
1948
1948
|
);
|
|
1949
|
-
|
|
1949
|
+
path7.resolve = function resolve(originPath, includePath, alreadyNormalized) {
|
|
1950
1950
|
if (!alreadyNormalized)
|
|
1951
1951
|
includePath = normalize(includePath);
|
|
1952
1952
|
if (isAbsolute(includePath))
|
|
@@ -2097,16 +2097,16 @@ var require_namespace = __commonJS({
|
|
|
2097
2097
|
object.onRemove(this);
|
|
2098
2098
|
return clearCache(this);
|
|
2099
2099
|
};
|
|
2100
|
-
Namespace.prototype.define = function define2(
|
|
2101
|
-
if (util.isString(
|
|
2102
|
-
|
|
2103
|
-
else if (!Array.isArray(
|
|
2100
|
+
Namespace.prototype.define = function define2(path7, json) {
|
|
2101
|
+
if (util.isString(path7))
|
|
2102
|
+
path7 = path7.split(".");
|
|
2103
|
+
else if (!Array.isArray(path7))
|
|
2104
2104
|
throw TypeError("illegal path");
|
|
2105
|
-
if (
|
|
2105
|
+
if (path7 && path7.length && path7[0] === "")
|
|
2106
2106
|
throw Error("path must be relative");
|
|
2107
2107
|
var ptr = this;
|
|
2108
|
-
while (
|
|
2109
|
-
var part =
|
|
2108
|
+
while (path7.length > 0) {
|
|
2109
|
+
var part = path7.shift();
|
|
2110
2110
|
if (ptr.nested && ptr.nested[part]) {
|
|
2111
2111
|
ptr = ptr.nested[part];
|
|
2112
2112
|
if (!(ptr instanceof Namespace))
|
|
@@ -2143,26 +2143,26 @@ var require_namespace = __commonJS({
|
|
|
2143
2143
|
});
|
|
2144
2144
|
return this;
|
|
2145
2145
|
};
|
|
2146
|
-
Namespace.prototype.lookup = function lookup(
|
|
2146
|
+
Namespace.prototype.lookup = function lookup(path7, filterTypes, parentAlreadyChecked) {
|
|
2147
2147
|
if (typeof filterTypes === "boolean") {
|
|
2148
2148
|
parentAlreadyChecked = filterTypes;
|
|
2149
2149
|
filterTypes = void 0;
|
|
2150
2150
|
} else if (filterTypes && !Array.isArray(filterTypes))
|
|
2151
2151
|
filterTypes = [filterTypes];
|
|
2152
|
-
if (util.isString(
|
|
2153
|
-
if (
|
|
2152
|
+
if (util.isString(path7) && path7.length) {
|
|
2153
|
+
if (path7 === ".")
|
|
2154
2154
|
return this.root;
|
|
2155
|
-
|
|
2156
|
-
} else if (!
|
|
2155
|
+
path7 = path7.split(".");
|
|
2156
|
+
} else if (!path7.length)
|
|
2157
2157
|
return this;
|
|
2158
|
-
var flatPath =
|
|
2159
|
-
if (
|
|
2160
|
-
return this.root.lookup(
|
|
2158
|
+
var flatPath = path7.join(".");
|
|
2159
|
+
if (path7[0] === "")
|
|
2160
|
+
return this.root.lookup(path7.slice(1), filterTypes);
|
|
2161
2161
|
var found = this.root._fullyQualifiedObjects && this.root._fullyQualifiedObjects["." + flatPath];
|
|
2162
2162
|
if (found && (!filterTypes || filterTypes.indexOf(found.constructor) > -1)) {
|
|
2163
2163
|
return found;
|
|
2164
2164
|
}
|
|
2165
|
-
found = this._lookupImpl(
|
|
2165
|
+
found = this._lookupImpl(path7, flatPath);
|
|
2166
2166
|
if (found && (!filterTypes || filterTypes.indexOf(found.constructor) > -1)) {
|
|
2167
2167
|
return found;
|
|
2168
2168
|
}
|
|
@@ -2170,7 +2170,7 @@ var require_namespace = __commonJS({
|
|
|
2170
2170
|
return null;
|
|
2171
2171
|
var current = this;
|
|
2172
2172
|
while (current.parent) {
|
|
2173
|
-
found = current.parent._lookupImpl(
|
|
2173
|
+
found = current.parent._lookupImpl(path7, flatPath);
|
|
2174
2174
|
if (found && (!filterTypes || filterTypes.indexOf(found.constructor) > -1)) {
|
|
2175
2175
|
return found;
|
|
2176
2176
|
}
|
|
@@ -2178,49 +2178,49 @@ var require_namespace = __commonJS({
|
|
|
2178
2178
|
}
|
|
2179
2179
|
return null;
|
|
2180
2180
|
};
|
|
2181
|
-
Namespace.prototype._lookupImpl = function lookup(
|
|
2181
|
+
Namespace.prototype._lookupImpl = function lookup(path7, flatPath) {
|
|
2182
2182
|
if (Object.prototype.hasOwnProperty.call(this._lookupCache, flatPath)) {
|
|
2183
2183
|
return this._lookupCache[flatPath];
|
|
2184
2184
|
}
|
|
2185
|
-
var found = this.get(
|
|
2185
|
+
var found = this.get(path7[0]);
|
|
2186
2186
|
var exact = null;
|
|
2187
2187
|
if (found) {
|
|
2188
|
-
if (
|
|
2188
|
+
if (path7.length === 1) {
|
|
2189
2189
|
exact = found;
|
|
2190
2190
|
} else if (found instanceof Namespace) {
|
|
2191
|
-
|
|
2192
|
-
exact = found._lookupImpl(
|
|
2191
|
+
path7 = path7.slice(1);
|
|
2192
|
+
exact = found._lookupImpl(path7, path7.join("."));
|
|
2193
2193
|
}
|
|
2194
2194
|
} else {
|
|
2195
2195
|
for (var i = 0; i < this.nestedArray.length; ++i)
|
|
2196
|
-
if (this._nestedArray[i] instanceof Namespace && (found = this._nestedArray[i]._lookupImpl(
|
|
2196
|
+
if (this._nestedArray[i] instanceof Namespace && (found = this._nestedArray[i]._lookupImpl(path7, flatPath)))
|
|
2197
2197
|
exact = found;
|
|
2198
2198
|
}
|
|
2199
2199
|
this._lookupCache[flatPath] = exact;
|
|
2200
2200
|
return exact;
|
|
2201
2201
|
};
|
|
2202
|
-
Namespace.prototype.lookupType = function lookupType(
|
|
2203
|
-
var found = this.lookup(
|
|
2202
|
+
Namespace.prototype.lookupType = function lookupType(path7) {
|
|
2203
|
+
var found = this.lookup(path7, [Type]);
|
|
2204
2204
|
if (!found)
|
|
2205
|
-
throw Error("no such type: " +
|
|
2205
|
+
throw Error("no such type: " + path7);
|
|
2206
2206
|
return found;
|
|
2207
2207
|
};
|
|
2208
|
-
Namespace.prototype.lookupEnum = function lookupEnum(
|
|
2209
|
-
var found = this.lookup(
|
|
2208
|
+
Namespace.prototype.lookupEnum = function lookupEnum(path7) {
|
|
2209
|
+
var found = this.lookup(path7, [Enum]);
|
|
2210
2210
|
if (!found)
|
|
2211
|
-
throw Error("no such Enum '" +
|
|
2211
|
+
throw Error("no such Enum '" + path7 + "' in " + this);
|
|
2212
2212
|
return found;
|
|
2213
2213
|
};
|
|
2214
|
-
Namespace.prototype.lookupTypeOrEnum = function lookupTypeOrEnum(
|
|
2215
|
-
var found = this.lookup(
|
|
2214
|
+
Namespace.prototype.lookupTypeOrEnum = function lookupTypeOrEnum(path7) {
|
|
2215
|
+
var found = this.lookup(path7, [Type, Enum]);
|
|
2216
2216
|
if (!found)
|
|
2217
|
-
throw Error("no such Type or Enum '" +
|
|
2217
|
+
throw Error("no such Type or Enum '" + path7 + "' in " + this);
|
|
2218
2218
|
return found;
|
|
2219
2219
|
};
|
|
2220
|
-
Namespace.prototype.lookupService = function lookupService(
|
|
2221
|
-
var found = this.lookup(
|
|
2220
|
+
Namespace.prototype.lookupService = function lookupService(path7) {
|
|
2221
|
+
var found = this.lookup(path7, [Service]);
|
|
2222
2222
|
if (!found)
|
|
2223
|
-
throw Error("no such Service '" +
|
|
2223
|
+
throw Error("no such Service '" + path7 + "' in " + this);
|
|
2224
2224
|
return found;
|
|
2225
2225
|
};
|
|
2226
2226
|
Namespace._configure = function(Type_, Service_, Enum_) {
|
|
@@ -3366,12 +3366,12 @@ var require_root = __commonJS({
|
|
|
3366
3366
|
if (parsed.imports) {
|
|
3367
3367
|
for (; i2 < parsed.imports.length; ++i2)
|
|
3368
3368
|
if (resolved2 = getBundledFileName(parsed.imports[i2]) || self2.resolvePath(filename2, parsed.imports[i2]))
|
|
3369
|
-
|
|
3369
|
+
fetch2(resolved2);
|
|
3370
3370
|
}
|
|
3371
3371
|
if (parsed.weakImports) {
|
|
3372
3372
|
for (i2 = 0; i2 < parsed.weakImports.length; ++i2)
|
|
3373
3373
|
if (resolved2 = getBundledFileName(parsed.weakImports[i2]) || self2.resolvePath(filename2, parsed.weakImports[i2]))
|
|
3374
|
-
|
|
3374
|
+
fetch2(resolved2, true);
|
|
3375
3375
|
}
|
|
3376
3376
|
}
|
|
3377
3377
|
} catch (err) {
|
|
@@ -3381,7 +3381,7 @@ var require_root = __commonJS({
|
|
|
3381
3381
|
finish(null, self2);
|
|
3382
3382
|
}
|
|
3383
3383
|
}
|
|
3384
|
-
function
|
|
3384
|
+
function fetch2(filename2, weak) {
|
|
3385
3385
|
filename2 = getBundledFileName(filename2) || filename2;
|
|
3386
3386
|
if (self2.files.indexOf(filename2) > -1) {
|
|
3387
3387
|
return;
|
|
@@ -3433,7 +3433,7 @@ var require_root = __commonJS({
|
|
|
3433
3433
|
}
|
|
3434
3434
|
for (var i = 0, resolved; i < filename.length; ++i)
|
|
3435
3435
|
if (resolved = self2.resolvePath("", filename[i]))
|
|
3436
|
-
|
|
3436
|
+
fetch2(resolved);
|
|
3437
3437
|
if (sync) {
|
|
3438
3438
|
self2.resolveAll();
|
|
3439
3439
|
return self2;
|
|
@@ -3621,14 +3621,14 @@ var require_util = __commonJS({
|
|
|
3621
3621
|
Object.defineProperty(object, "$type", { value: enm, enumerable: false });
|
|
3622
3622
|
return enm;
|
|
3623
3623
|
};
|
|
3624
|
-
util.setProperty = function setProperty(dst,
|
|
3625
|
-
function setProp(dst2,
|
|
3626
|
-
var part =
|
|
3624
|
+
util.setProperty = function setProperty(dst, path7, value, ifNotSet) {
|
|
3625
|
+
function setProp(dst2, path8, value2) {
|
|
3626
|
+
var part = path8.shift();
|
|
3627
3627
|
if (part === "__proto__" || part === "prototype") {
|
|
3628
3628
|
return dst2;
|
|
3629
3629
|
}
|
|
3630
|
-
if (
|
|
3631
|
-
dst2[part] = setProp(dst2[part] || {},
|
|
3630
|
+
if (path8.length > 0) {
|
|
3631
|
+
dst2[part] = setProp(dst2[part] || {}, path8, value2);
|
|
3632
3632
|
} else {
|
|
3633
3633
|
var prevValue = dst2[part];
|
|
3634
3634
|
if (prevValue && ifNotSet)
|
|
@@ -3641,10 +3641,10 @@ var require_util = __commonJS({
|
|
|
3641
3641
|
}
|
|
3642
3642
|
if (typeof dst !== "object")
|
|
3643
3643
|
throw TypeError("dst must be an object");
|
|
3644
|
-
if (!
|
|
3644
|
+
if (!path7)
|
|
3645
3645
|
throw TypeError("path must be specified");
|
|
3646
|
-
|
|
3647
|
-
return setProp(dst,
|
|
3646
|
+
path7 = path7.split(".");
|
|
3647
|
+
return setProp(dst, path7, value);
|
|
3648
3648
|
};
|
|
3649
3649
|
Object.defineProperty(util, "decorateRoot", {
|
|
3650
3650
|
get: function() {
|
|
@@ -4191,12 +4191,12 @@ var require_object = __commonJS({
|
|
|
4191
4191
|
*/
|
|
4192
4192
|
fullName: {
|
|
4193
4193
|
get: function() {
|
|
4194
|
-
var
|
|
4194
|
+
var path7 = [this.name], ptr = this.parent;
|
|
4195
4195
|
while (ptr) {
|
|
4196
|
-
|
|
4196
|
+
path7.unshift(ptr.name);
|
|
4197
4197
|
ptr = ptr.parent;
|
|
4198
4198
|
}
|
|
4199
|
-
return
|
|
4199
|
+
return path7.join(".");
|
|
4200
4200
|
}
|
|
4201
4201
|
}
|
|
4202
4202
|
});
|
|
@@ -8178,19 +8178,19 @@ var require_util2 = __commonJS({
|
|
|
8178
8178
|
"use strict";
|
|
8179
8179
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
8180
8180
|
exports2.addCommonProtos = exports2.loadProtosWithOptionsSync = exports2.loadProtosWithOptions = void 0;
|
|
8181
|
-
var
|
|
8182
|
-
var
|
|
8181
|
+
var fs5 = __require("fs");
|
|
8182
|
+
var path7 = __require("path");
|
|
8183
8183
|
var Protobuf = require_protobufjs();
|
|
8184
8184
|
function addIncludePathResolver(root, includePaths) {
|
|
8185
8185
|
const originalResolvePath = root.resolvePath;
|
|
8186
8186
|
root.resolvePath = (origin, target) => {
|
|
8187
|
-
if (
|
|
8187
|
+
if (path7.isAbsolute(target)) {
|
|
8188
8188
|
return target;
|
|
8189
8189
|
}
|
|
8190
8190
|
for (const directory of includePaths) {
|
|
8191
|
-
const fullPath =
|
|
8191
|
+
const fullPath = path7.join(directory, target);
|
|
8192
8192
|
try {
|
|
8193
|
-
|
|
8193
|
+
fs5.accessSync(fullPath, fs5.constants.R_OK);
|
|
8194
8194
|
return fullPath;
|
|
8195
8195
|
} catch (err) {
|
|
8196
8196
|
continue;
|
|
@@ -9505,6 +9505,8 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
|
9505
9505
|
ErrorCode2[ErrorCode2["TTL_EXPIRED"] = 13] = "TTL_EXPIRED";
|
|
9506
9506
|
ErrorCode2[ErrorCode2["DUPLICATE_DETECTED"] = 14] = "DUPLICATE_DETECTED";
|
|
9507
9507
|
ErrorCode2[ErrorCode2["ALREADY_IN_PROGRESS"] = 15] = "ALREADY_IN_PROGRESS";
|
|
9508
|
+
ErrorCode2[ErrorCode2["OVERLOADED"] = 16] = "OVERLOADED";
|
|
9509
|
+
ErrorCode2[ErrorCode2["REDIRECT"] = 20] = "REDIRECT";
|
|
9508
9510
|
ErrorCode2[ErrorCode2["INTERNAL_ERROR"] = 99] = "INTERNAL_ERROR";
|
|
9509
9511
|
return ErrorCode2;
|
|
9510
9512
|
})(ErrorCode || {});
|
|
@@ -10779,6 +10781,38 @@ var HandoffStatus = /* @__PURE__ */ ((HandoffStatus2) => {
|
|
|
10779
10781
|
HandoffStatus2[HandoffStatus2["EXPIRED"] = 5] = "EXPIRED";
|
|
10780
10782
|
return HandoffStatus2;
|
|
10781
10783
|
})(HandoffStatus || {});
|
|
10784
|
+
var DEFAULT_MAX_RETRIES_ON_OVERLOADED = 2;
|
|
10785
|
+
var DEFAULT_INITIAL_BACKOFF_MS = 250;
|
|
10786
|
+
var DEFAULT_BACKOFF_MULTIPLIER = 2;
|
|
10787
|
+
var DEFAULT_MAX_BACKOFF_MS = 2e3;
|
|
10788
|
+
var DEFAULT_MAX_REDIRECTS = 0;
|
|
10789
|
+
function defaultDelegationPolicy() {
|
|
10790
|
+
return {
|
|
10791
|
+
maxRetriesOnOverloaded: DEFAULT_MAX_RETRIES_ON_OVERLOADED,
|
|
10792
|
+
initialBackoffMs: DEFAULT_INITIAL_BACKOFF_MS,
|
|
10793
|
+
backoffMultiplier: DEFAULT_BACKOFF_MULTIPLIER,
|
|
10794
|
+
maxBackoffMs: DEFAULT_MAX_BACKOFF_MS,
|
|
10795
|
+
allowSpilloverRouting: false,
|
|
10796
|
+
maxRedirects: DEFAULT_MAX_REDIRECTS
|
|
10797
|
+
};
|
|
10798
|
+
}
|
|
10799
|
+
function normalizeDelegationPolicy(policy) {
|
|
10800
|
+
const defaults = defaultDelegationPolicy();
|
|
10801
|
+
const maxRetriesOnOverloaded = policy?.maxRetriesOnOverloaded;
|
|
10802
|
+
const initialBackoffMs = policy?.initialBackoffMs;
|
|
10803
|
+
const backoffMultiplier = policy?.backoffMultiplier;
|
|
10804
|
+
const maxBackoffMs = policy?.maxBackoffMs;
|
|
10805
|
+
const allowSpilloverRouting = policy?.allowSpilloverRouting;
|
|
10806
|
+
const maxRedirects = policy?.maxRedirects;
|
|
10807
|
+
return {
|
|
10808
|
+
maxRetriesOnOverloaded: maxRetriesOnOverloaded === void 0 ? defaults.maxRetriesOnOverloaded : maxRetriesOnOverloaded,
|
|
10809
|
+
initialBackoffMs: initialBackoffMs !== void 0 && initialBackoffMs > 0 ? initialBackoffMs : defaults.initialBackoffMs,
|
|
10810
|
+
backoffMultiplier: backoffMultiplier !== void 0 && backoffMultiplier > 0 ? backoffMultiplier : defaults.backoffMultiplier,
|
|
10811
|
+
maxBackoffMs: maxBackoffMs !== void 0 && maxBackoffMs > 0 ? maxBackoffMs : defaults.maxBackoffMs,
|
|
10812
|
+
allowSpilloverRouting: allowSpilloverRouting ?? defaults.allowSpilloverRouting,
|
|
10813
|
+
maxRedirects: maxRedirects === void 0 ? defaults.maxRedirects : maxRedirects
|
|
10814
|
+
};
|
|
10815
|
+
}
|
|
10782
10816
|
var HandoffValidationError = class extends Error {
|
|
10783
10817
|
constructor(message) {
|
|
10784
10818
|
super(message);
|
|
@@ -10827,9 +10861,15 @@ var HandoffClient = class {
|
|
|
10827
10861
|
`Handoff request with ID '${request.requestId}' already exists`
|
|
10828
10862
|
);
|
|
10829
10863
|
}
|
|
10864
|
+
if (request.budget !== void 0 && (!request.budget.deadlineEpochMs || request.budget.deadlineEpochMs <= 0)) {
|
|
10865
|
+
throw new HandoffValidationError(
|
|
10866
|
+
"budget.deadlineEpochMs is required for cross-swarm delegation"
|
|
10867
|
+
);
|
|
10868
|
+
}
|
|
10830
10869
|
const requestWithTimestamp = {
|
|
10831
10870
|
...request,
|
|
10832
|
-
createdAt: request.createdAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
10871
|
+
createdAt: request.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
10872
|
+
delegationPolicy: request.budget !== void 0 || request.delegationPolicy !== void 0 ? normalizeDelegationPolicy(request.delegationPolicy) : void 0
|
|
10833
10873
|
};
|
|
10834
10874
|
this.requests.set(request.requestId, requestWithTimestamp);
|
|
10835
10875
|
if (request.toAgent) {
|
|
@@ -10851,7 +10891,8 @@ var HandoffClient = class {
|
|
|
10851
10891
|
const timeoutResponse = {
|
|
10852
10892
|
requestId: request.requestId,
|
|
10853
10893
|
accepted: false,
|
|
10854
|
-
rejectionReason: `Handoff request timed out after ${timeoutMs}ms
|
|
10894
|
+
rejectionReason: `Handoff request timed out after ${timeoutMs}ms`,
|
|
10895
|
+
rejectionCode: 3 /* ACK_TIMEOUT */
|
|
10855
10896
|
};
|
|
10856
10897
|
this.responses.set(request.requestId, timeoutResponse);
|
|
10857
10898
|
if (request.toAgent && this.pendingByAgent.has(request.toAgent)) {
|
|
@@ -10916,7 +10957,7 @@ var HandoffClient = class {
|
|
|
10916
10957
|
* await client.rejectHandoff("handoff-123", "Agent at capacity");
|
|
10917
10958
|
* ```
|
|
10918
10959
|
*/
|
|
10919
|
-
async rejectHandoff(handoffId, reason) {
|
|
10960
|
+
async rejectHandoff(handoffId, reason, options) {
|
|
10920
10961
|
const request = this.requests.get(handoffId);
|
|
10921
10962
|
if (!request) {
|
|
10922
10963
|
throw new HandoffValidationError(
|
|
@@ -10931,7 +10972,10 @@ var HandoffClient = class {
|
|
|
10931
10972
|
const response = {
|
|
10932
10973
|
requestId: handoffId,
|
|
10933
10974
|
accepted: false,
|
|
10934
|
-
rejectionReason: reason
|
|
10975
|
+
rejectionReason: reason,
|
|
10976
|
+
rejectionCode: options?.rejectionCode ?? 0 /* ERROR_CODE_UNSPECIFIED */,
|
|
10977
|
+
retryAfterMs: options?.retryAfterMs,
|
|
10978
|
+
redirectToAgentId: options?.redirectToAgentId
|
|
10935
10979
|
};
|
|
10936
10980
|
this.responses.set(handoffId, response);
|
|
10937
10981
|
if (request.toAgent && this.pendingByAgent.has(request.toAgent)) {
|
|
@@ -11435,8 +11479,8 @@ function nowHlcStub(date = /* @__PURE__ */ new Date()) {
|
|
|
11435
11479
|
const wallUs = date.getTime() * 1e3;
|
|
11436
11480
|
let node = "unknown";
|
|
11437
11481
|
try {
|
|
11438
|
-
const
|
|
11439
|
-
node =
|
|
11482
|
+
const os3 = __require("node:os");
|
|
11483
|
+
node = os3.hostname() || "unknown";
|
|
11440
11484
|
} catch {
|
|
11441
11485
|
node = "browser";
|
|
11442
11486
|
}
|
|
@@ -11893,6 +11937,542 @@ function decodeBase64(b64) {
|
|
|
11893
11937
|
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
11894
11938
|
}
|
|
11895
11939
|
|
|
11940
|
+
// src/runtime/delegation.ts
|
|
11941
|
+
import { randomUUID } from "node:crypto";
|
|
11942
|
+
var RETRY_AFTER_JITTER_RATIO = 0.2;
|
|
11943
|
+
var DEFAULT_EFFECTIVE_MAX_REDIRECTS = 2;
|
|
11944
|
+
function defaultNowMs() {
|
|
11945
|
+
return Date.now();
|
|
11946
|
+
}
|
|
11947
|
+
function defaultSleepMs(milliseconds) {
|
|
11948
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
11949
|
+
}
|
|
11950
|
+
function defaultRandUniform(low, high) {
|
|
11951
|
+
if (high <= low) {
|
|
11952
|
+
return low;
|
|
11953
|
+
}
|
|
11954
|
+
return low + Math.random() * (high - low);
|
|
11955
|
+
}
|
|
11956
|
+
function normalizePolicy(policy) {
|
|
11957
|
+
return {
|
|
11958
|
+
maxRetriesOnOverloaded: policy?.maxRetriesOnOverloaded ?? DEFAULT_MAX_RETRIES_ON_OVERLOADED,
|
|
11959
|
+
initialBackoffMs: policy?.initialBackoffMs !== void 0 && policy.initialBackoffMs > 0 ? policy.initialBackoffMs : DEFAULT_INITIAL_BACKOFF_MS,
|
|
11960
|
+
backoffMultiplier: policy?.backoffMultiplier !== void 0 && policy.backoffMultiplier > 0 ? policy.backoffMultiplier : DEFAULT_BACKOFF_MULTIPLIER,
|
|
11961
|
+
maxBackoffMs: policy?.maxBackoffMs !== void 0 && policy.maxBackoffMs > 0 ? policy.maxBackoffMs : DEFAULT_MAX_BACKOFF_MS,
|
|
11962
|
+
allowSpilloverRouting: policy?.allowSpilloverRouting ?? false,
|
|
11963
|
+
maxRedirects: policy?.maxRedirects ?? DEFAULT_MAX_REDIRECTS
|
|
11964
|
+
};
|
|
11965
|
+
}
|
|
11966
|
+
function deadlineExhaustedResponse(requestId) {
|
|
11967
|
+
return {
|
|
11968
|
+
requestId,
|
|
11969
|
+
accepted: false,
|
|
11970
|
+
rejectionReason: "Delegation deadline exhausted before handoff acceptance",
|
|
11971
|
+
rejectionCode: 3 /* ACK_TIMEOUT */
|
|
11972
|
+
};
|
|
11973
|
+
}
|
|
11974
|
+
function invalidRedirectResponse(requestId, reason) {
|
|
11975
|
+
return {
|
|
11976
|
+
requestId,
|
|
11977
|
+
accepted: false,
|
|
11978
|
+
rejectionReason: reason,
|
|
11979
|
+
rejectionCode: 6 /* VALIDATION_ERROR */
|
|
11980
|
+
};
|
|
11981
|
+
}
|
|
11982
|
+
function effectiveMaxRedirects(policy) {
|
|
11983
|
+
const configured = policy.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
11984
|
+
return configured > 0 ? configured : DEFAULT_EFFECTIVE_MAX_REDIRECTS;
|
|
11985
|
+
}
|
|
11986
|
+
function nextRetryWaitMs(response, retryIndex, policy, randUniformFn) {
|
|
11987
|
+
if (response.retryAfterMs !== void 0 && response.retryAfterMs > 0) {
|
|
11988
|
+
const retryAfter = response.retryAfterMs;
|
|
11989
|
+
const jitter = randUniformFn(0, retryAfter * RETRY_AFTER_JITTER_RATIO);
|
|
11990
|
+
return Math.floor(retryAfter + jitter);
|
|
11991
|
+
}
|
|
11992
|
+
const initialBackoffMs = policy.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS;
|
|
11993
|
+
const backoffMultiplier = policy.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER;
|
|
11994
|
+
const maxBackoffMs = policy.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
|
|
11995
|
+
const exponential = initialBackoffMs * backoffMultiplier ** retryIndex;
|
|
11996
|
+
const bounded = Math.min(exponential, maxBackoffMs);
|
|
11997
|
+
return Math.floor(randUniformFn(0, bounded));
|
|
11998
|
+
}
|
|
11999
|
+
function deductWallTime(request, elapsedMs) {
|
|
12000
|
+
if (request.budget?.wallTimeRemainingMs !== void 0) {
|
|
12001
|
+
request.budget.wallTimeRemainingMs = Math.max(
|
|
12002
|
+
request.budget.wallTimeRemainingMs - elapsedMs,
|
|
12003
|
+
0
|
|
12004
|
+
);
|
|
12005
|
+
}
|
|
12006
|
+
}
|
|
12007
|
+
function budgetExhausted(budget, nowMs) {
|
|
12008
|
+
return budget.wallTimeRemainingMs !== void 0 && budget.wallTimeRemainingMs <= 0 || nowMs > budget.deadlineEpochMs;
|
|
12009
|
+
}
|
|
12010
|
+
async function delegateToSwarm(options) {
|
|
12011
|
+
if (!options.budget.deadlineEpochMs || options.budget.deadlineEpochMs <= 0) {
|
|
12012
|
+
throw new HandoffValidationError(
|
|
12013
|
+
"budget.deadlineEpochMs is required for cross-swarm delegation"
|
|
12014
|
+
);
|
|
12015
|
+
}
|
|
12016
|
+
if (options.timeoutMs !== void 0 && options.timeoutMs < 0) {
|
|
12017
|
+
throw new HandoffValidationError("timeoutMs must be >= 0");
|
|
12018
|
+
}
|
|
12019
|
+
const nowMs = options.nowMsFn ?? defaultNowMs;
|
|
12020
|
+
const sleepMs = options.sleepMsFn ?? defaultSleepMs;
|
|
12021
|
+
const randUniform = options.randUniformFn ?? defaultRandUniform;
|
|
12022
|
+
const policy = normalizePolicy(options.delegationPolicy);
|
|
12023
|
+
const requestBudget = { ...options.budget };
|
|
12024
|
+
const request = {
|
|
12025
|
+
requestId: options.requestId ?? randomUUID(),
|
|
12026
|
+
fromAgent: options.fromAgent,
|
|
12027
|
+
toAgent: options.toAgent,
|
|
12028
|
+
reason: options.reason,
|
|
12029
|
+
contextSnapshot: options.contextSnapshot ?? new Uint8Array(),
|
|
12030
|
+
capabilitiesRequired: [...options.capabilitiesRequired ?? []],
|
|
12031
|
+
priority: options.priority ?? 0,
|
|
12032
|
+
timeoutMs: options.timeoutMs,
|
|
12033
|
+
budget: requestBudget,
|
|
12034
|
+
delegationPolicy: policy
|
|
12035
|
+
};
|
|
12036
|
+
const maxRetriesOnOverloaded = policy.maxRetriesOnOverloaded ?? DEFAULT_MAX_RETRIES_ON_OVERLOADED;
|
|
12037
|
+
let retryIndex = 0;
|
|
12038
|
+
let redirectHops = 0;
|
|
12039
|
+
const redirectBound = effectiveMaxRedirects(policy);
|
|
12040
|
+
const visitedAgents = /* @__PURE__ */ new Set([request.toAgent]);
|
|
12041
|
+
while (true) {
|
|
12042
|
+
const startMs = nowMs();
|
|
12043
|
+
if (budgetExhausted(request.budget, startMs)) {
|
|
12044
|
+
return deadlineExhaustedResponse(request.requestId);
|
|
12045
|
+
}
|
|
12046
|
+
const response = await options.sendHandoffFn(request);
|
|
12047
|
+
const endMs = nowMs();
|
|
12048
|
+
const elapsedMs = Math.max(endMs - startMs, 0);
|
|
12049
|
+
deductWallTime(request, elapsedMs);
|
|
12050
|
+
if (response.accepted) {
|
|
12051
|
+
return response;
|
|
12052
|
+
}
|
|
12053
|
+
if (budgetExhausted(request.budget, endMs)) {
|
|
12054
|
+
return deadlineExhaustedResponse(request.requestId);
|
|
12055
|
+
}
|
|
12056
|
+
if (response.rejectionCode !== 16 /* OVERLOADED */) {
|
|
12057
|
+
if (response.rejectionCode !== 20 /* REDIRECT */) {
|
|
12058
|
+
return response;
|
|
12059
|
+
}
|
|
12060
|
+
if (!policy.allowSpilloverRouting) {
|
|
12061
|
+
return response;
|
|
12062
|
+
}
|
|
12063
|
+
const targetAgent = response.redirectToAgentId?.trim();
|
|
12064
|
+
if (!targetAgent) {
|
|
12065
|
+
return invalidRedirectResponse(
|
|
12066
|
+
request.requestId,
|
|
12067
|
+
"Redirect response missing non-empty redirectToAgentId"
|
|
12068
|
+
);
|
|
12069
|
+
}
|
|
12070
|
+
if (visitedAgents.has(targetAgent)) {
|
|
12071
|
+
return invalidRedirectResponse(
|
|
12072
|
+
request.requestId,
|
|
12073
|
+
`Redirect loop detected for agent '${targetAgent}'`
|
|
12074
|
+
);
|
|
12075
|
+
}
|
|
12076
|
+
if (redirectHops >= redirectBound) {
|
|
12077
|
+
return response;
|
|
12078
|
+
}
|
|
12079
|
+
request.toAgent = targetAgent;
|
|
12080
|
+
visitedAgents.add(targetAgent);
|
|
12081
|
+
redirectHops += 1;
|
|
12082
|
+
continue;
|
|
12083
|
+
}
|
|
12084
|
+
if (retryIndex >= maxRetriesOnOverloaded) {
|
|
12085
|
+
return response;
|
|
12086
|
+
}
|
|
12087
|
+
const waitMs = nextRetryWaitMs(response, retryIndex, policy, randUniform);
|
|
12088
|
+
retryIndex += 1;
|
|
12089
|
+
const remainingDeadlineMs = request.budget.deadlineEpochMs - endMs;
|
|
12090
|
+
const remainingWallTimeMs = request.budget?.wallTimeRemainingMs;
|
|
12091
|
+
if (waitMs <= 0 || remainingWallTimeMs !== void 0 && waitMs > remainingWallTimeMs || waitMs > remainingDeadlineMs) {
|
|
12092
|
+
return response;
|
|
12093
|
+
}
|
|
12094
|
+
const beforeSleepMs = nowMs();
|
|
12095
|
+
await sleepMs(waitMs);
|
|
12096
|
+
const afterSleepMs = nowMs();
|
|
12097
|
+
deductWallTime(request, Math.max(afterSleepMs - beforeSleepMs, 0));
|
|
12098
|
+
}
|
|
12099
|
+
}
|
|
12100
|
+
|
|
12101
|
+
// src/runtime/cancellation.ts
|
|
12102
|
+
var MIN_GRACE_PERIOD_MS = 5e3;
|
|
12103
|
+
var CancellationValidationError = class extends Error {
|
|
12104
|
+
constructor(message) {
|
|
12105
|
+
super(message);
|
|
12106
|
+
this.name = "CancellationValidationError";
|
|
12107
|
+
}
|
|
12108
|
+
};
|
|
12109
|
+
function defaultNowMs2() {
|
|
12110
|
+
return Date.now();
|
|
12111
|
+
}
|
|
12112
|
+
function normalizeCorrelationId(correlationId) {
|
|
12113
|
+
const normalized = correlationId.trim();
|
|
12114
|
+
if (!normalized) {
|
|
12115
|
+
throw new CancellationValidationError(
|
|
12116
|
+
"cancel.correlationId is required and must be a non-empty string"
|
|
12117
|
+
);
|
|
12118
|
+
}
|
|
12119
|
+
return normalized;
|
|
12120
|
+
}
|
|
12121
|
+
function normalizeReason(reason) {
|
|
12122
|
+
if (reason === void 0) {
|
|
12123
|
+
return "";
|
|
12124
|
+
}
|
|
12125
|
+
return reason.trim();
|
|
12126
|
+
}
|
|
12127
|
+
function normalizeGracePeriodMs(gracePeriodMs) {
|
|
12128
|
+
const requested = gracePeriodMs !== void 0 && Number.isFinite(gracePeriodMs) && gracePeriodMs > 0 ? Math.floor(gracePeriodMs) : 0;
|
|
12129
|
+
return Math.max(requested, MIN_GRACE_PERIOD_MS);
|
|
12130
|
+
}
|
|
12131
|
+
function normalizeMetadataValue(value) {
|
|
12132
|
+
if (value === null) {
|
|
12133
|
+
return null;
|
|
12134
|
+
}
|
|
12135
|
+
if (typeof value === "string") {
|
|
12136
|
+
return value.trim();
|
|
12137
|
+
}
|
|
12138
|
+
if (typeof value === "number") {
|
|
12139
|
+
return Number.isFinite(value) ? value : void 0;
|
|
12140
|
+
}
|
|
12141
|
+
if (typeof value === "boolean") {
|
|
12142
|
+
return value;
|
|
12143
|
+
}
|
|
12144
|
+
if (value instanceof Date) {
|
|
12145
|
+
return value.toISOString();
|
|
12146
|
+
}
|
|
12147
|
+
if (typeof value === "bigint") {
|
|
12148
|
+
return value.toString();
|
|
12149
|
+
}
|
|
12150
|
+
if (typeof value === "object") {
|
|
12151
|
+
try {
|
|
12152
|
+
return JSON.stringify(value);
|
|
12153
|
+
} catch {
|
|
12154
|
+
return void 0;
|
|
12155
|
+
}
|
|
12156
|
+
}
|
|
12157
|
+
return void 0;
|
|
12158
|
+
}
|
|
12159
|
+
function normalizeMetadata(metadata, options) {
|
|
12160
|
+
const { reason, gracePeriodMs } = options;
|
|
12161
|
+
const normalized = {};
|
|
12162
|
+
if (metadata !== void 0) {
|
|
12163
|
+
for (const [rawKey, rawValue] of Object.entries(metadata)) {
|
|
12164
|
+
const key = rawKey.trim();
|
|
12165
|
+
if (!key) {
|
|
12166
|
+
continue;
|
|
12167
|
+
}
|
|
12168
|
+
const value = normalizeMetadataValue(rawValue);
|
|
12169
|
+
if (value !== void 0) {
|
|
12170
|
+
normalized[key] = value;
|
|
12171
|
+
}
|
|
12172
|
+
}
|
|
12173
|
+
}
|
|
12174
|
+
normalized.reason = reason;
|
|
12175
|
+
normalized.grace_period_ms = gracePeriodMs;
|
|
12176
|
+
return normalized;
|
|
12177
|
+
}
|
|
12178
|
+
function cloneMetadata(metadata) {
|
|
12179
|
+
return { ...metadata };
|
|
12180
|
+
}
|
|
12181
|
+
function cloneFlag(flag) {
|
|
12182
|
+
return {
|
|
12183
|
+
cancelled: flag.cancelled,
|
|
12184
|
+
gracePeriodMs: flag.gracePeriodMs,
|
|
12185
|
+
cancelTimeMs: flag.cancelTimeMs,
|
|
12186
|
+
metadata: cloneMetadata(flag.metadata)
|
|
12187
|
+
};
|
|
12188
|
+
}
|
|
12189
|
+
var CancellationManager = class {
|
|
12190
|
+
childDelegations = /* @__PURE__ */ new Map();
|
|
12191
|
+
cancellationFlags = /* @__PURE__ */ new Map();
|
|
12192
|
+
nowMs;
|
|
12193
|
+
constructor(options = {}) {
|
|
12194
|
+
this.nowMs = options.nowMsFn ?? defaultNowMs2;
|
|
12195
|
+
}
|
|
12196
|
+
registerChildDelegation(parentCorrelationId, childCorrelationId) {
|
|
12197
|
+
const parent = normalizeCorrelationId(parentCorrelationId);
|
|
12198
|
+
const child = normalizeCorrelationId(childCorrelationId);
|
|
12199
|
+
if (!this.childDelegations.has(parent)) {
|
|
12200
|
+
this.childDelegations.set(parent, /* @__PURE__ */ new Set());
|
|
12201
|
+
}
|
|
12202
|
+
this.childDelegations.get(parent).add(child);
|
|
12203
|
+
}
|
|
12204
|
+
handleCancelDelegation(cancel) {
|
|
12205
|
+
const correlationId = normalizeCorrelationId(cancel.correlationId);
|
|
12206
|
+
const gracePeriodMs = normalizeGracePeriodMs(cancel.gracePeriodMs);
|
|
12207
|
+
const reason = normalizeReason(cancel.reason);
|
|
12208
|
+
const cancelTimeMs = this.nowMs();
|
|
12209
|
+
const metadata = normalizeMetadata(cancel.metadata, {
|
|
12210
|
+
reason,
|
|
12211
|
+
gracePeriodMs
|
|
12212
|
+
});
|
|
12213
|
+
const flag = {
|
|
12214
|
+
cancelled: true,
|
|
12215
|
+
gracePeriodMs,
|
|
12216
|
+
cancelTimeMs,
|
|
12217
|
+
metadata
|
|
12218
|
+
};
|
|
12219
|
+
this.cancellationFlags.set(correlationId, cloneFlag(flag));
|
|
12220
|
+
for (const childCorrelationId of this.childDelegations.get(correlationId) ?? []) {
|
|
12221
|
+
this.cancellationFlags.set(childCorrelationId, cloneFlag(flag));
|
|
12222
|
+
}
|
|
12223
|
+
return {
|
|
12224
|
+
acknowledged: true,
|
|
12225
|
+
correlationId,
|
|
12226
|
+
gracePeriodMs,
|
|
12227
|
+
message: "Cancellation recorded",
|
|
12228
|
+
metadata: cloneMetadata(metadata)
|
|
12229
|
+
};
|
|
12230
|
+
}
|
|
12231
|
+
isCancelled(correlationId) {
|
|
12232
|
+
const normalized = correlationId.trim();
|
|
12233
|
+
if (!normalized) {
|
|
12234
|
+
return false;
|
|
12235
|
+
}
|
|
12236
|
+
const entry = this.cancellationFlags.get(normalized);
|
|
12237
|
+
return Boolean(entry?.cancelled);
|
|
12238
|
+
}
|
|
12239
|
+
isGraceExpired(correlationId, nowMs) {
|
|
12240
|
+
const normalized = correlationId.trim();
|
|
12241
|
+
if (!normalized) {
|
|
12242
|
+
return false;
|
|
12243
|
+
}
|
|
12244
|
+
const entry = this.cancellationFlags.get(normalized);
|
|
12245
|
+
if (entry === void 0 || !entry.cancelled) {
|
|
12246
|
+
return false;
|
|
12247
|
+
}
|
|
12248
|
+
const currentMs = nowMs ?? this.nowMs();
|
|
12249
|
+
return currentMs - entry.cancelTimeMs >= entry.gracePeriodMs;
|
|
12250
|
+
}
|
|
12251
|
+
forcedPreemptionErrorCode(correlationId, nowMs) {
|
|
12252
|
+
if (this.isGraceExpired(correlationId, nowMs)) {
|
|
12253
|
+
return 12 /* FORCED_PREEMPTION */;
|
|
12254
|
+
}
|
|
12255
|
+
return 0 /* ERROR_CODE_UNSPECIFIED */;
|
|
12256
|
+
}
|
|
12257
|
+
collectForcedPreemptions(correlationIds, nowMs) {
|
|
12258
|
+
const currentMs = nowMs ?? this.nowMs();
|
|
12259
|
+
const forced = /* @__PURE__ */ new Set();
|
|
12260
|
+
for (const correlationId of correlationIds) {
|
|
12261
|
+
if (this.isGraceExpired(correlationId, currentMs)) {
|
|
12262
|
+
forced.add(correlationId);
|
|
12263
|
+
}
|
|
12264
|
+
}
|
|
12265
|
+
return forced;
|
|
12266
|
+
}
|
|
12267
|
+
};
|
|
12268
|
+
|
|
12269
|
+
// src/runtime/gateway.ts
|
|
12270
|
+
var REGISTRATION_TYPE_STANDARD_AGENT = 1;
|
|
12271
|
+
var REGISTRATION_TYPE_SWARM_GATEWAY = 2;
|
|
12272
|
+
var AGENT_STATE_INITIALIZING = 1;
|
|
12273
|
+
var AGENT_STATE_RUNNING = 4;
|
|
12274
|
+
var AGENT_STATE_FAILED = 10;
|
|
12275
|
+
var AGENT_STATE_SHUTTING_DOWN = 11;
|
|
12276
|
+
var DEFAULT_PEER_LIVENESS_THRESHOLD_MS = 3e4;
|
|
12277
|
+
var NON_SERVING_AGENT_STATES = /* @__PURE__ */ new Set([
|
|
12278
|
+
AGENT_STATE_INITIALIZING,
|
|
12279
|
+
AGENT_STATE_FAILED,
|
|
12280
|
+
AGENT_STATE_SHUTTING_DOWN
|
|
12281
|
+
]);
|
|
12282
|
+
var GatewayValidationError = class extends Error {
|
|
12283
|
+
constructor(message) {
|
|
12284
|
+
super(message);
|
|
12285
|
+
this.name = "GatewayValidationError";
|
|
12286
|
+
}
|
|
12287
|
+
};
|
|
12288
|
+
function defaultNowMs3() {
|
|
12289
|
+
return Date.now();
|
|
12290
|
+
}
|
|
12291
|
+
function sameCapabilities(peerCapabilities, localCapabilities) {
|
|
12292
|
+
const peerSet = new Set(peerCapabilities);
|
|
12293
|
+
if (peerSet.size !== localCapabilities.size) {
|
|
12294
|
+
return false;
|
|
12295
|
+
}
|
|
12296
|
+
for (const localCapability of localCapabilities) {
|
|
12297
|
+
if (!peerSet.has(localCapability)) {
|
|
12298
|
+
return false;
|
|
12299
|
+
}
|
|
12300
|
+
}
|
|
12301
|
+
return true;
|
|
12302
|
+
}
|
|
12303
|
+
var PeerSelector = class {
|
|
12304
|
+
localAgentId;
|
|
12305
|
+
localCapabilities;
|
|
12306
|
+
nowMs;
|
|
12307
|
+
peerHealthFn;
|
|
12308
|
+
livenessThresholdMs;
|
|
12309
|
+
peers = [];
|
|
12310
|
+
runtime = /* @__PURE__ */ new Map();
|
|
12311
|
+
rrCursor = 0;
|
|
12312
|
+
constructor(options) {
|
|
12313
|
+
const threshold = options.livenessThresholdMs ?? DEFAULT_PEER_LIVENESS_THRESHOLD_MS;
|
|
12314
|
+
if (threshold < 0) {
|
|
12315
|
+
throw new GatewayValidationError("livenessThresholdMs must be >= 0");
|
|
12316
|
+
}
|
|
12317
|
+
this.localAgentId = options.localAgentId;
|
|
12318
|
+
this.localCapabilities = new Set(options.localCapabilities ?? []);
|
|
12319
|
+
this.nowMs = options.nowMsFn ?? defaultNowMs3;
|
|
12320
|
+
this.peerHealthFn = options.peerHealthFn ?? (() => true);
|
|
12321
|
+
this.livenessThresholdMs = threshold;
|
|
12322
|
+
}
|
|
12323
|
+
setPeers(peers) {
|
|
12324
|
+
this.peers = [...peers];
|
|
12325
|
+
const activeIds = new Set(this.peers.map((peer) => peer.agentId));
|
|
12326
|
+
for (const agentId of [...this.runtime.keys()]) {
|
|
12327
|
+
if (!activeIds.has(agentId)) {
|
|
12328
|
+
this.runtime.delete(agentId);
|
|
12329
|
+
}
|
|
12330
|
+
}
|
|
12331
|
+
const nowMs = this.nowMs();
|
|
12332
|
+
for (const peer of this.peers) {
|
|
12333
|
+
if (!this.runtime.has(peer.agentId)) {
|
|
12334
|
+
this.runtime.set(peer.agentId, {
|
|
12335
|
+
state: AGENT_STATE_RUNNING,
|
|
12336
|
+
lastHeartbeatMs: nowMs,
|
|
12337
|
+
cooldownUntilMs: 0
|
|
12338
|
+
});
|
|
12339
|
+
}
|
|
12340
|
+
}
|
|
12341
|
+
}
|
|
12342
|
+
updatePeerRuntimeState(agentId, options = {}) {
|
|
12343
|
+
const runtime = this.ensureRuntime(agentId);
|
|
12344
|
+
if (options.state !== void 0) {
|
|
12345
|
+
runtime.state = options.state;
|
|
12346
|
+
}
|
|
12347
|
+
if (options.lastHeartbeatMs !== void 0) {
|
|
12348
|
+
runtime.lastHeartbeatMs = Math.max(Math.floor(options.lastHeartbeatMs), 0);
|
|
12349
|
+
}
|
|
12350
|
+
if (options.cooldownUntilMs !== void 0) {
|
|
12351
|
+
runtime.cooldownUntilMs = Math.max(Math.floor(options.cooldownUntilMs), 0);
|
|
12352
|
+
}
|
|
12353
|
+
this.runtime.set(agentId, runtime);
|
|
12354
|
+
}
|
|
12355
|
+
touchPeerHeartbeat(agentId, options = {}) {
|
|
12356
|
+
this.updatePeerRuntimeState(agentId, {
|
|
12357
|
+
state: options.state,
|
|
12358
|
+
lastHeartbeatMs: options.nowMs ?? this.nowMs()
|
|
12359
|
+
});
|
|
12360
|
+
}
|
|
12361
|
+
recordPeerOverloaded(agentId, options = {}) {
|
|
12362
|
+
const runtime = this.ensureRuntime(agentId);
|
|
12363
|
+
const nowMs = this.nowMs();
|
|
12364
|
+
const retryAfterMs = Math.max(Math.floor(options.retryAfterMs ?? 0), 0);
|
|
12365
|
+
const localCooldownMs = Math.max(Math.floor(options.localCooldownMs ?? 0), 0);
|
|
12366
|
+
const cooldownMs = Math.max(retryAfterMs, localCooldownMs);
|
|
12367
|
+
runtime.cooldownUntilMs = Math.max(runtime.cooldownUntilMs, nowMs + cooldownMs);
|
|
12368
|
+
this.runtime.set(agentId, runtime);
|
|
12369
|
+
}
|
|
12370
|
+
isEligible(peer) {
|
|
12371
|
+
if (peer.registrationType !== REGISTRATION_TYPE_SWARM_GATEWAY) {
|
|
12372
|
+
return false;
|
|
12373
|
+
}
|
|
12374
|
+
if (peer.agentId === this.localAgentId) {
|
|
12375
|
+
return false;
|
|
12376
|
+
}
|
|
12377
|
+
return sameCapabilities(peer.capabilities, this.localCapabilities);
|
|
12378
|
+
}
|
|
12379
|
+
selectPeer() {
|
|
12380
|
+
const healthy = this.peers.filter((peer) => this.isHealthy(peer));
|
|
12381
|
+
if (healthy.length === 0) {
|
|
12382
|
+
return "";
|
|
12383
|
+
}
|
|
12384
|
+
const idx = this.rrCursor % healthy.length;
|
|
12385
|
+
this.rrCursor += 1;
|
|
12386
|
+
return healthy[idx].agentId;
|
|
12387
|
+
}
|
|
12388
|
+
ensureRuntime(agentId) {
|
|
12389
|
+
const existing = this.runtime.get(agentId);
|
|
12390
|
+
if (existing !== void 0) {
|
|
12391
|
+
return existing;
|
|
12392
|
+
}
|
|
12393
|
+
return {
|
|
12394
|
+
state: AGENT_STATE_RUNNING,
|
|
12395
|
+
lastHeartbeatMs: this.nowMs(),
|
|
12396
|
+
cooldownUntilMs: 0
|
|
12397
|
+
};
|
|
12398
|
+
}
|
|
12399
|
+
isHealthy(peer) {
|
|
12400
|
+
if (!this.isEligible(peer)) {
|
|
12401
|
+
return false;
|
|
12402
|
+
}
|
|
12403
|
+
if (!this.peerHealthFn(peer)) {
|
|
12404
|
+
return false;
|
|
12405
|
+
}
|
|
12406
|
+
const runtime = this.runtime.get(peer.agentId);
|
|
12407
|
+
if (runtime === void 0) {
|
|
12408
|
+
return false;
|
|
12409
|
+
}
|
|
12410
|
+
const nowMs = this.nowMs();
|
|
12411
|
+
if (runtime.cooldownUntilMs > nowMs) {
|
|
12412
|
+
return false;
|
|
12413
|
+
}
|
|
12414
|
+
if (nowMs - runtime.lastHeartbeatMs > this.livenessThresholdMs) {
|
|
12415
|
+
return false;
|
|
12416
|
+
}
|
|
12417
|
+
if (NON_SERVING_AGENT_STATES.has(runtime.state)) {
|
|
12418
|
+
return false;
|
|
12419
|
+
}
|
|
12420
|
+
return true;
|
|
12421
|
+
}
|
|
12422
|
+
};
|
|
12423
|
+
var GatewayRedirectEmitter = class {
|
|
12424
|
+
retryAfterMs;
|
|
12425
|
+
peerSelector;
|
|
12426
|
+
constructor(options) {
|
|
12427
|
+
if ((options.retryAfterMs ?? 0) < 0) {
|
|
12428
|
+
throw new GatewayValidationError("retryAfterMs must be >= 0");
|
|
12429
|
+
}
|
|
12430
|
+
this.retryAfterMs = options.retryAfterMs ?? 1e3;
|
|
12431
|
+
this.peerSelector = new PeerSelector({
|
|
12432
|
+
localAgentId: options.agentId,
|
|
12433
|
+
localCapabilities: options.capabilities ?? [],
|
|
12434
|
+
nowMsFn: options.nowMsFn,
|
|
12435
|
+
peerHealthFn: options.peerHealthFn,
|
|
12436
|
+
livenessThresholdMs: options.peerLivenessThresholdMs
|
|
12437
|
+
});
|
|
12438
|
+
this.peerSelector.setPeers(options.peerDescriptors ?? []);
|
|
12439
|
+
}
|
|
12440
|
+
setPeerDescriptors(peers) {
|
|
12441
|
+
this.peerSelector.setPeers(peers);
|
|
12442
|
+
}
|
|
12443
|
+
updatePeerRuntimeState(agentId, options = {}) {
|
|
12444
|
+
this.peerSelector.updatePeerRuntimeState(agentId, options);
|
|
12445
|
+
}
|
|
12446
|
+
touchPeerHeartbeat(agentId, options = {}) {
|
|
12447
|
+
this.peerSelector.touchPeerHeartbeat(agentId, options);
|
|
12448
|
+
}
|
|
12449
|
+
recordPeerOverloaded(agentId, options = {}) {
|
|
12450
|
+
this.peerSelector.recordPeerOverloaded(agentId, options);
|
|
12451
|
+
}
|
|
12452
|
+
emitOverloadedResponse(request) {
|
|
12453
|
+
if (request.delegationPolicy?.allowSpilloverRouting) {
|
|
12454
|
+
const redirectToAgentId = this.peerSelector.selectPeer();
|
|
12455
|
+
if (redirectToAgentId) {
|
|
12456
|
+
return {
|
|
12457
|
+
requestId: request.requestId,
|
|
12458
|
+
accepted: false,
|
|
12459
|
+
rejectionReason: "Gateway at capacity; redirect to peer gateway",
|
|
12460
|
+
rejectionCode: 20 /* REDIRECT */,
|
|
12461
|
+
retryAfterMs: 0,
|
|
12462
|
+
redirectToAgentId
|
|
12463
|
+
};
|
|
12464
|
+
}
|
|
12465
|
+
}
|
|
12466
|
+
return {
|
|
12467
|
+
requestId: request.requestId,
|
|
12468
|
+
accepted: false,
|
|
12469
|
+
rejectionReason: "Gateway at capacity",
|
|
12470
|
+
rejectionCode: 16 /* OVERLOADED */,
|
|
12471
|
+
retryAfterMs: this.retryAfterMs
|
|
12472
|
+
};
|
|
12473
|
+
}
|
|
12474
|
+
};
|
|
12475
|
+
|
|
11896
12476
|
// src/persistence/persistence.ts
|
|
11897
12477
|
import fsp from "node:fs/promises";
|
|
11898
12478
|
import path2 from "node:path";
|
|
@@ -14022,8 +14602,8 @@ function defaultPath() {
|
|
|
14022
14602
|
}
|
|
14023
14603
|
var FileBackend = class {
|
|
14024
14604
|
path;
|
|
14025
|
-
constructor(
|
|
14026
|
-
this.path =
|
|
14605
|
+
constructor(path7) {
|
|
14606
|
+
this.path = path7 ?? defaultPath();
|
|
14027
14607
|
mkdirSync2(dirname2(this.path), { recursive: true });
|
|
14028
14608
|
try {
|
|
14029
14609
|
chmodSync(dirname2(this.path), 448);
|
|
@@ -14153,30 +14733,879 @@ async function selectBackend(mode) {
|
|
|
14153
14733
|
}
|
|
14154
14734
|
}
|
|
14155
14735
|
|
|
14736
|
+
// src/llm/client.ts
|
|
14737
|
+
var LlmError = class extends Error {
|
|
14738
|
+
constructor(message) {
|
|
14739
|
+
super(message);
|
|
14740
|
+
this.name = "LlmError";
|
|
14741
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
14742
|
+
}
|
|
14743
|
+
};
|
|
14744
|
+
var LlmAuthenticationError = class extends LlmError {
|
|
14745
|
+
constructor(message) {
|
|
14746
|
+
super(message);
|
|
14747
|
+
this.name = "LlmAuthenticationError";
|
|
14748
|
+
}
|
|
14749
|
+
};
|
|
14750
|
+
var LlmRateLimitError = class extends LlmError {
|
|
14751
|
+
constructor(message) {
|
|
14752
|
+
super(message);
|
|
14753
|
+
this.name = "LlmRateLimitError";
|
|
14754
|
+
}
|
|
14755
|
+
};
|
|
14756
|
+
var LlmTimeoutError = class extends LlmError {
|
|
14757
|
+
constructor(message) {
|
|
14758
|
+
super(message);
|
|
14759
|
+
this.name = "LlmTimeoutError";
|
|
14760
|
+
}
|
|
14761
|
+
};
|
|
14762
|
+
var LlmContextLengthError = class extends LlmError {
|
|
14763
|
+
constructor(message) {
|
|
14764
|
+
super(message);
|
|
14765
|
+
this.name = "LlmContextLengthError";
|
|
14766
|
+
}
|
|
14767
|
+
};
|
|
14768
|
+
|
|
14769
|
+
// src/llm/rateLimiter.ts
|
|
14770
|
+
function envBool(name, defaultVal) {
|
|
14771
|
+
const raw = (process.env[name] ?? defaultVal).toLowerCase();
|
|
14772
|
+
return !["0", "false", "no"].includes(raw);
|
|
14773
|
+
}
|
|
14774
|
+
function buildRateLimiterConfig(overrides) {
|
|
14775
|
+
return {
|
|
14776
|
+
tokensPerMinute: overrides?.tokensPerMinute ?? parseInt(process.env["LLM_RATE_LIMIT_TOKENS_PER_MIN"] ?? "250000", 10),
|
|
14777
|
+
burstAllowance: overrides?.burstAllowance ?? 1,
|
|
14778
|
+
minTokensPerRequest: overrides?.minTokensPerRequest ?? 100,
|
|
14779
|
+
maxWaitSeconds: overrides?.maxWaitSeconds ?? 120,
|
|
14780
|
+
enabled: overrides?.enabled ?? envBool("LLM_RATE_LIMIT_ENABLED", "1"),
|
|
14781
|
+
adaptiveEnabled: overrides?.adaptiveEnabled ?? envBool("LLM_RATE_LIMIT_ADAPTIVE", "1"),
|
|
14782
|
+
reductionFactor: overrides?.reductionFactor ?? parseFloat(process.env["LLM_RATE_LIMIT_REDUCTION_FACTOR"] ?? "0.7"),
|
|
14783
|
+
recoveryFactor: overrides?.recoveryFactor ?? parseFloat(process.env["LLM_RATE_LIMIT_RECOVERY_FACTOR"] ?? "1.1"),
|
|
14784
|
+
cooldownSeconds: overrides?.cooldownSeconds ?? parseFloat(process.env["LLM_RATE_LIMIT_COOLDOWN_SECONDS"] ?? "30"),
|
|
14785
|
+
successesForRecovery: overrides?.successesForRecovery ?? parseInt(
|
|
14786
|
+
process.env["LLM_RATE_LIMIT_RECOVERY_SUCCESS_THRESHOLD"] ?? "20",
|
|
14787
|
+
10
|
|
14788
|
+
)
|
|
14789
|
+
};
|
|
14790
|
+
}
|
|
14791
|
+
var TokenBucket = class {
|
|
14792
|
+
config;
|
|
14793
|
+
baseTpm;
|
|
14794
|
+
currentTpm;
|
|
14795
|
+
minTpm;
|
|
14796
|
+
tokens;
|
|
14797
|
+
lastRefill;
|
|
14798
|
+
lastRateLimitTime = null;
|
|
14799
|
+
successesSinceLimit = 0;
|
|
14800
|
+
constructor(config) {
|
|
14801
|
+
this.config = buildRateLimiterConfig(config);
|
|
14802
|
+
this.baseTpm = this.config.tokensPerMinute;
|
|
14803
|
+
this.currentTpm = this.config.tokensPerMinute;
|
|
14804
|
+
this.minTpm = Math.max(1e3, this.baseTpm * 0.25);
|
|
14805
|
+
this.tokens = this.currentTpm;
|
|
14806
|
+
this.lastRefill = performance.now();
|
|
14807
|
+
}
|
|
14808
|
+
// -- Internal helpers ----------------------------------------------------
|
|
14809
|
+
refill() {
|
|
14810
|
+
const now = performance.now();
|
|
14811
|
+
const elapsedSeconds = (now - this.lastRefill) / 1e3;
|
|
14812
|
+
const refill = elapsedSeconds * (this.currentTpm / 60);
|
|
14813
|
+
this.tokens = Math.min(
|
|
14814
|
+
this.tokens + refill,
|
|
14815
|
+
this.currentTpm * this.config.burstAllowance
|
|
14816
|
+
);
|
|
14817
|
+
this.lastRefill = now;
|
|
14818
|
+
this.maybeRecover(now);
|
|
14819
|
+
}
|
|
14820
|
+
maybeRecover(now) {
|
|
14821
|
+
if (!this.config.adaptiveEnabled)
|
|
14822
|
+
return;
|
|
14823
|
+
if (this.currentTpm >= this.baseTpm)
|
|
14824
|
+
return;
|
|
14825
|
+
if (this.lastRateLimitTime === null)
|
|
14826
|
+
return;
|
|
14827
|
+
if ((now - this.lastRateLimitTime) / 1e3 < this.config.cooldownSeconds)
|
|
14828
|
+
return;
|
|
14829
|
+
if (this.successesSinceLimit < this.config.successesForRecovery)
|
|
14830
|
+
return;
|
|
14831
|
+
const newLimit = Math.min(
|
|
14832
|
+
this.baseTpm,
|
|
14833
|
+
this.currentTpm * this.config.recoveryFactor
|
|
14834
|
+
);
|
|
14835
|
+
if (newLimit > this.currentTpm) {
|
|
14836
|
+
this.currentTpm = newLimit;
|
|
14837
|
+
this.successesSinceLimit = 0;
|
|
14838
|
+
}
|
|
14839
|
+
}
|
|
14840
|
+
// -- Public API ----------------------------------------------------------
|
|
14841
|
+
/**
|
|
14842
|
+
* Acquire tokens, waiting if necessary.
|
|
14843
|
+
*
|
|
14844
|
+
* @param estimatedTokens - Estimated token count for the request.
|
|
14845
|
+
* @returns Time spent waiting in milliseconds (0 if immediate).
|
|
14846
|
+
* @throws Error if waiting exceeds {@link RateLimiterConfig.maxWaitSeconds}.
|
|
14847
|
+
*/
|
|
14848
|
+
async acquire(estimatedTokens) {
|
|
14849
|
+
if (!this.config.enabled)
|
|
14850
|
+
return 0;
|
|
14851
|
+
estimatedTokens = Math.max(estimatedTokens, this.config.minTokensPerRequest);
|
|
14852
|
+
const waitStart = performance.now();
|
|
14853
|
+
for (; ; ) {
|
|
14854
|
+
this.refill();
|
|
14855
|
+
if (this.tokens >= estimatedTokens) {
|
|
14856
|
+
this.tokens -= estimatedTokens;
|
|
14857
|
+
return performance.now() - waitStart;
|
|
14858
|
+
}
|
|
14859
|
+
const elapsed = (performance.now() - waitStart) / 1e3;
|
|
14860
|
+
if (elapsed >= this.config.maxWaitSeconds) {
|
|
14861
|
+
throw new Error(
|
|
14862
|
+
`Rate limiter: waited ${elapsed.toFixed(1)}s for ${estimatedTokens} tokens`
|
|
14863
|
+
);
|
|
14864
|
+
}
|
|
14865
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
14866
|
+
}
|
|
14867
|
+
}
|
|
14868
|
+
/**
|
|
14869
|
+
* Record a 429 event -- adaptively reduce budget.
|
|
14870
|
+
*
|
|
14871
|
+
* Call this when the upstream API returns HTTP 429 or an equivalent
|
|
14872
|
+
* rate-limit error.
|
|
14873
|
+
*/
|
|
14874
|
+
recordRateLimit() {
|
|
14875
|
+
if (!(this.config.enabled && this.config.adaptiveEnabled))
|
|
14876
|
+
return;
|
|
14877
|
+
this.lastRateLimitTime = performance.now();
|
|
14878
|
+
this.successesSinceLimit = 0;
|
|
14879
|
+
const newLimit = Math.max(
|
|
14880
|
+
this.minTpm,
|
|
14881
|
+
this.currentTpm * this.config.reductionFactor
|
|
14882
|
+
);
|
|
14883
|
+
if (newLimit < this.currentTpm) {
|
|
14884
|
+
this.currentTpm = newLimit;
|
|
14885
|
+
this.tokens = Math.min(this.tokens, this.currentTpm);
|
|
14886
|
+
}
|
|
14887
|
+
}
|
|
14888
|
+
/**
|
|
14889
|
+
* Record a successful request for adaptive recovery.
|
|
14890
|
+
*
|
|
14891
|
+
* Call this after each successful API response.
|
|
14892
|
+
*/
|
|
14893
|
+
recordSuccess() {
|
|
14894
|
+
if (!(this.config.enabled && this.config.adaptiveEnabled))
|
|
14895
|
+
return;
|
|
14896
|
+
this.successesSinceLimit += 1;
|
|
14897
|
+
}
|
|
14898
|
+
/** Current available token count. */
|
|
14899
|
+
get availableTokens() {
|
|
14900
|
+
return this.tokens;
|
|
14901
|
+
}
|
|
14902
|
+
/** Current tokens-per-minute budget (may be reduced after 429). */
|
|
14903
|
+
get currentTokensPerMinute() {
|
|
14904
|
+
return this.currentTpm;
|
|
14905
|
+
}
|
|
14906
|
+
};
|
|
14907
|
+
var globalBucket = null;
|
|
14908
|
+
function getGlobalRateLimiter(config) {
|
|
14909
|
+
if (globalBucket === null) {
|
|
14910
|
+
globalBucket = new TokenBucket(config);
|
|
14911
|
+
}
|
|
14912
|
+
return globalBucket;
|
|
14913
|
+
}
|
|
14914
|
+
function resetGlobalRateLimiter() {
|
|
14915
|
+
globalBucket = null;
|
|
14916
|
+
}
|
|
14917
|
+
|
|
14918
|
+
// src/llm/mock.ts
|
|
14919
|
+
var MockLlmClient = class {
|
|
14920
|
+
/** The model name returned in responses. */
|
|
14921
|
+
defaultModel;
|
|
14922
|
+
responses;
|
|
14923
|
+
responseIndex = 0;
|
|
14924
|
+
responseGenerator;
|
|
14925
|
+
_callCount = 0;
|
|
14926
|
+
_callHistory = [];
|
|
14927
|
+
constructor(opts) {
|
|
14928
|
+
this.defaultModel = opts?.defaultModel ?? "mock-model";
|
|
14929
|
+
this.responses = opts?.responses ?? [];
|
|
14930
|
+
this.responseGenerator = opts?.responseGenerator;
|
|
14931
|
+
}
|
|
14932
|
+
/** Number of queries made to this client. */
|
|
14933
|
+
get callCount() {
|
|
14934
|
+
return this._callCount;
|
|
14935
|
+
}
|
|
14936
|
+
/** History of all calls made to this client. */
|
|
14937
|
+
get callHistory() {
|
|
14938
|
+
return this._callHistory;
|
|
14939
|
+
}
|
|
14940
|
+
/** Reset call count, history, and response index. */
|
|
14941
|
+
reset() {
|
|
14942
|
+
this._callCount = 0;
|
|
14943
|
+
this._callHistory.length = 0;
|
|
14944
|
+
this.responseIndex = 0;
|
|
14945
|
+
}
|
|
14946
|
+
/**
|
|
14947
|
+
* Return a mock response.
|
|
14948
|
+
*
|
|
14949
|
+
* Priority for determining response content:
|
|
14950
|
+
* 1. `responseGenerator` function (if provided)
|
|
14951
|
+
* 2. `responses` array (cycles through entries)
|
|
14952
|
+
* 3. Default echo: "Mock response to: <prompt>"
|
|
14953
|
+
*
|
|
14954
|
+
* @param prompt - The prompt (recorded in history).
|
|
14955
|
+
* @param opts - Optional query configuration (recorded in history).
|
|
14956
|
+
* @returns LlmResponse with mock content.
|
|
14957
|
+
*/
|
|
14958
|
+
async query(prompt, opts) {
|
|
14959
|
+
const model = opts?.model ?? this.defaultModel;
|
|
14960
|
+
const maxTokens = opts?.maxTokens ?? 4096;
|
|
14961
|
+
const temperature = opts?.temperature ?? 1;
|
|
14962
|
+
this._callCount += 1;
|
|
14963
|
+
this._callHistory.push({
|
|
14964
|
+
prompt,
|
|
14965
|
+
systemPrompt: opts?.systemPrompt,
|
|
14966
|
+
maxTokens,
|
|
14967
|
+
temperature,
|
|
14968
|
+
model
|
|
14969
|
+
});
|
|
14970
|
+
let content;
|
|
14971
|
+
if (this.responseGenerator) {
|
|
14972
|
+
content = this.responseGenerator(prompt);
|
|
14973
|
+
} else if (this.responses.length > 0) {
|
|
14974
|
+
content = this.responses[this.responseIndex % this.responses.length];
|
|
14975
|
+
this.responseIndex += 1;
|
|
14976
|
+
} else {
|
|
14977
|
+
content = `Mock response to: ${prompt.slice(0, 100)}`;
|
|
14978
|
+
}
|
|
14979
|
+
return {
|
|
14980
|
+
content,
|
|
14981
|
+
model,
|
|
14982
|
+
usage: {
|
|
14983
|
+
input_tokens: Math.max(Math.floor(prompt.length / 4), 1),
|
|
14984
|
+
output_tokens: Math.max(Math.floor(content.length / 4), 1)
|
|
14985
|
+
},
|
|
14986
|
+
metadata: { mock: true, call_count: this._callCount }
|
|
14987
|
+
};
|
|
14988
|
+
}
|
|
14989
|
+
/**
|
|
14990
|
+
* Stream a mock response.
|
|
14991
|
+
*
|
|
14992
|
+
* Yields the response word by word to simulate streaming.
|
|
14993
|
+
*
|
|
14994
|
+
* @param prompt - The prompt (recorded in history).
|
|
14995
|
+
* @param opts - Optional query configuration.
|
|
14996
|
+
* @yields Words from the mock response.
|
|
14997
|
+
*/
|
|
14998
|
+
async *streamQuery(prompt, opts) {
|
|
14999
|
+
const response = await this.query(prompt, opts);
|
|
15000
|
+
const words = response.content.split(" ");
|
|
15001
|
+
for (let i = 0; i < words.length; i++) {
|
|
15002
|
+
yield words[i] + (i < words.length - 1 ? " " : "");
|
|
15003
|
+
}
|
|
15004
|
+
}
|
|
15005
|
+
};
|
|
15006
|
+
|
|
15007
|
+
// src/llm/groq.ts
|
|
15008
|
+
import fs3 from "node:fs";
|
|
15009
|
+
import os from "node:os";
|
|
15010
|
+
import path5 from "node:path";
|
|
15011
|
+
var DEFAULT_MODEL = "llama-3.3-70b-versatile";
|
|
15012
|
+
var BASE_URL = "https://api.groq.com/openai/v1/chat/completions";
|
|
15013
|
+
var GroqClient = class _GroqClient {
|
|
15014
|
+
/** The resolved API key. */
|
|
15015
|
+
apiKey;
|
|
15016
|
+
/** The default model used when none is specified per-call. */
|
|
15017
|
+
defaultModel;
|
|
15018
|
+
timeoutMs;
|
|
15019
|
+
rateLimiter;
|
|
15020
|
+
constructor(opts) {
|
|
15021
|
+
this.defaultModel = opts?.defaultModel ?? process.env["GROQ_DEFAULT_MODEL"] ?? DEFAULT_MODEL;
|
|
15022
|
+
const envKey = process.env["GROQ_API_KEY"]?.trim();
|
|
15023
|
+
const resolved = opts?.apiKey ?? envKey ?? _GroqClient.loadKeyFile();
|
|
15024
|
+
if (!resolved) {
|
|
15025
|
+
throw new LlmAuthenticationError(
|
|
15026
|
+
"No Groq API key. Set GROQ_API_KEY, pass apiKey, or create ~/.groq"
|
|
15027
|
+
);
|
|
15028
|
+
}
|
|
15029
|
+
this.apiKey = resolved;
|
|
15030
|
+
this.timeoutMs = opts?.timeoutMs ?? 12e4;
|
|
15031
|
+
this.rateLimiter = getGlobalRateLimiter();
|
|
15032
|
+
}
|
|
15033
|
+
/**
|
|
15034
|
+
* Load API key from ~/.groq file.
|
|
15035
|
+
*
|
|
15036
|
+
* @returns The key string, or null if the file does not exist.
|
|
15037
|
+
*/
|
|
15038
|
+
static loadKeyFile() {
|
|
15039
|
+
try {
|
|
15040
|
+
const keyPath = path5.join(os.homedir(), ".groq");
|
|
15041
|
+
if (fs3.existsSync(keyPath)) {
|
|
15042
|
+
return fs3.readFileSync(keyPath, "utf-8").trim();
|
|
15043
|
+
}
|
|
15044
|
+
} catch {
|
|
15045
|
+
}
|
|
15046
|
+
return null;
|
|
15047
|
+
}
|
|
15048
|
+
// -- Helpers -------------------------------------------------------------
|
|
15049
|
+
estimateTokens(prompt, systemPrompt) {
|
|
15050
|
+
let total = prompt.length;
|
|
15051
|
+
if (systemPrompt)
|
|
15052
|
+
total += systemPrompt.length;
|
|
15053
|
+
return Math.max(Math.floor(total / 4), 100);
|
|
15054
|
+
}
|
|
15055
|
+
buildMessages(prompt, systemPrompt) {
|
|
15056
|
+
const messages = [];
|
|
15057
|
+
if (systemPrompt) {
|
|
15058
|
+
messages.push({ role: "system", content: systemPrompt });
|
|
15059
|
+
}
|
|
15060
|
+
messages.push({ role: "user", content: prompt });
|
|
15061
|
+
return messages;
|
|
15062
|
+
}
|
|
15063
|
+
/**
|
|
15064
|
+
* Map an HTTP error response to the appropriate LlmError subclass.
|
|
15065
|
+
*
|
|
15066
|
+
* @param status - HTTP status code.
|
|
15067
|
+
* @param body - Parsed response body (if available).
|
|
15068
|
+
* @param message - Raw error message.
|
|
15069
|
+
*/
|
|
15070
|
+
mapError(status, body, message) {
|
|
15071
|
+
if (status === 401 || status === 403) {
|
|
15072
|
+
return new LlmAuthenticationError(`Groq auth failed (${status}): ${message}`);
|
|
15073
|
+
}
|
|
15074
|
+
if (status === 429) {
|
|
15075
|
+
this.rateLimiter.recordRateLimit();
|
|
15076
|
+
return new LlmRateLimitError(`Groq rate limit exceeded: ${message}`);
|
|
15077
|
+
}
|
|
15078
|
+
if (status === 408 || status === 504) {
|
|
15079
|
+
return new LlmTimeoutError(`Groq request timed out (${status}): ${message}`);
|
|
15080
|
+
}
|
|
15081
|
+
const errorObj = body?.["error"];
|
|
15082
|
+
const code = errorObj?.["code"];
|
|
15083
|
+
if (code === "context_length_exceeded") {
|
|
15084
|
+
return new LlmContextLengthError(`Groq context length exceeded: ${message}`);
|
|
15085
|
+
}
|
|
15086
|
+
return new LlmError(`Groq API error (${status}): ${message}`);
|
|
15087
|
+
}
|
|
15088
|
+
// -- Public API ----------------------------------------------------------
|
|
15089
|
+
/**
|
|
15090
|
+
* Send a query to Groq and get a complete response.
|
|
15091
|
+
*
|
|
15092
|
+
* @param prompt - The user prompt/query.
|
|
15093
|
+
* @param opts - Optional query configuration.
|
|
15094
|
+
* @returns LlmResponse with generated content and metadata.
|
|
15095
|
+
*/
|
|
15096
|
+
async query(prompt, opts) {
|
|
15097
|
+
const useModel = opts?.model ?? this.defaultModel;
|
|
15098
|
+
const maxTokens = opts?.maxTokens ?? 4096;
|
|
15099
|
+
const temperature = opts?.temperature ?? 1;
|
|
15100
|
+
const systemPrompt = opts?.systemPrompt;
|
|
15101
|
+
const estimated = this.estimateTokens(prompt, systemPrompt);
|
|
15102
|
+
await this.rateLimiter.acquire(estimated);
|
|
15103
|
+
const body = {
|
|
15104
|
+
model: useModel,
|
|
15105
|
+
messages: this.buildMessages(prompt, systemPrompt),
|
|
15106
|
+
max_tokens: maxTokens,
|
|
15107
|
+
temperature
|
|
15108
|
+
};
|
|
15109
|
+
let response;
|
|
15110
|
+
try {
|
|
15111
|
+
const controller = new AbortController();
|
|
15112
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
15113
|
+
response = await fetch(BASE_URL, {
|
|
15114
|
+
method: "POST",
|
|
15115
|
+
headers: {
|
|
15116
|
+
"Content-Type": "application/json",
|
|
15117
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
15118
|
+
},
|
|
15119
|
+
body: JSON.stringify(body),
|
|
15120
|
+
signal: controller.signal
|
|
15121
|
+
});
|
|
15122
|
+
clearTimeout(timer);
|
|
15123
|
+
} catch (err) {
|
|
15124
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
15125
|
+
throw new LlmTimeoutError(`Groq request timed out after ${this.timeoutMs}ms`);
|
|
15126
|
+
}
|
|
15127
|
+
throw new LlmError(`Groq network error: ${String(err)}`);
|
|
15128
|
+
}
|
|
15129
|
+
if (!response.ok) {
|
|
15130
|
+
let errorBody = null;
|
|
15131
|
+
let errorMessage = response.statusText;
|
|
15132
|
+
try {
|
|
15133
|
+
errorBody = await response.json();
|
|
15134
|
+
const errorObj = errorBody?.["error"];
|
|
15135
|
+
errorMessage = errorObj?.["message"] ?? errorMessage;
|
|
15136
|
+
} catch {
|
|
15137
|
+
}
|
|
15138
|
+
throw this.mapError(response.status, errorBody, errorMessage);
|
|
15139
|
+
}
|
|
15140
|
+
const data = await response.json();
|
|
15141
|
+
this.rateLimiter.recordSuccess();
|
|
15142
|
+
const choices = data["choices"];
|
|
15143
|
+
const firstChoice = choices?.[0];
|
|
15144
|
+
const messageObj = firstChoice?.["message"];
|
|
15145
|
+
const content = messageObj?.["content"] ?? "";
|
|
15146
|
+
const usageObj = data["usage"];
|
|
15147
|
+
let usage;
|
|
15148
|
+
if (usageObj) {
|
|
15149
|
+
usage = {
|
|
15150
|
+
input_tokens: usageObj["prompt_tokens"] ?? 0,
|
|
15151
|
+
output_tokens: usageObj["completion_tokens"] ?? 0
|
|
15152
|
+
};
|
|
15153
|
+
}
|
|
15154
|
+
return {
|
|
15155
|
+
content,
|
|
15156
|
+
model: data["model"] ?? useModel,
|
|
15157
|
+
usage
|
|
15158
|
+
};
|
|
15159
|
+
}
|
|
15160
|
+
/**
|
|
15161
|
+
* Stream a query response chunk by chunk.
|
|
15162
|
+
*
|
|
15163
|
+
* Uses Server-Sent Events (SSE) streaming from the Groq API.
|
|
15164
|
+
*
|
|
15165
|
+
* @param prompt - The user prompt/query.
|
|
15166
|
+
* @param opts - Optional query configuration.
|
|
15167
|
+
* @yields Text chunks as they arrive from the API.
|
|
15168
|
+
*/
|
|
15169
|
+
async *streamQuery(prompt, opts) {
|
|
15170
|
+
const useModel = opts?.model ?? this.defaultModel;
|
|
15171
|
+
const maxTokens = opts?.maxTokens ?? 4096;
|
|
15172
|
+
const temperature = opts?.temperature ?? 1;
|
|
15173
|
+
const systemPrompt = opts?.systemPrompt;
|
|
15174
|
+
const estimated = this.estimateTokens(prompt, systemPrompt);
|
|
15175
|
+
await this.rateLimiter.acquire(estimated);
|
|
15176
|
+
const body = {
|
|
15177
|
+
model: useModel,
|
|
15178
|
+
messages: this.buildMessages(prompt, systemPrompt),
|
|
15179
|
+
max_tokens: maxTokens,
|
|
15180
|
+
temperature,
|
|
15181
|
+
stream: true
|
|
15182
|
+
};
|
|
15183
|
+
let response;
|
|
15184
|
+
try {
|
|
15185
|
+
const controller = new AbortController();
|
|
15186
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
15187
|
+
response = await fetch(BASE_URL, {
|
|
15188
|
+
method: "POST",
|
|
15189
|
+
headers: {
|
|
15190
|
+
"Content-Type": "application/json",
|
|
15191
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
15192
|
+
},
|
|
15193
|
+
body: JSON.stringify(body),
|
|
15194
|
+
signal: controller.signal
|
|
15195
|
+
});
|
|
15196
|
+
clearTimeout(timer);
|
|
15197
|
+
} catch (err) {
|
|
15198
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
15199
|
+
throw new LlmTimeoutError(`Groq request timed out after ${this.timeoutMs}ms`);
|
|
15200
|
+
}
|
|
15201
|
+
throw new LlmError(`Groq network error: ${String(err)}`);
|
|
15202
|
+
}
|
|
15203
|
+
if (!response.ok) {
|
|
15204
|
+
let errorBody = null;
|
|
15205
|
+
let errorMessage = response.statusText;
|
|
15206
|
+
try {
|
|
15207
|
+
errorBody = await response.json();
|
|
15208
|
+
const errorObj = errorBody?.["error"];
|
|
15209
|
+
errorMessage = errorObj?.["message"] ?? errorMessage;
|
|
15210
|
+
} catch {
|
|
15211
|
+
}
|
|
15212
|
+
throw this.mapError(response.status, errorBody, errorMessage);
|
|
15213
|
+
}
|
|
15214
|
+
if (!response.body) {
|
|
15215
|
+
throw new LlmError("Groq streaming response has no body");
|
|
15216
|
+
}
|
|
15217
|
+
const reader = response.body.getReader();
|
|
15218
|
+
const decoder = new TextDecoder();
|
|
15219
|
+
let buffer = "";
|
|
15220
|
+
let streamCompleted = false;
|
|
15221
|
+
try {
|
|
15222
|
+
for (; ; ) {
|
|
15223
|
+
const { done, value } = await reader.read();
|
|
15224
|
+
if (done)
|
|
15225
|
+
break;
|
|
15226
|
+
buffer += decoder.decode(value, { stream: true });
|
|
15227
|
+
const lines = buffer.split("\n");
|
|
15228
|
+
buffer = lines.pop() ?? "";
|
|
15229
|
+
for (const line of lines) {
|
|
15230
|
+
const trimmed = line.trim();
|
|
15231
|
+
if (!trimmed || !trimmed.startsWith("data: "))
|
|
15232
|
+
continue;
|
|
15233
|
+
const payload = trimmed.slice(6);
|
|
15234
|
+
if (payload === "[DONE]") {
|
|
15235
|
+
streamCompleted = true;
|
|
15236
|
+
return;
|
|
15237
|
+
}
|
|
15238
|
+
try {
|
|
15239
|
+
const chunk = JSON.parse(payload);
|
|
15240
|
+
const choices = chunk["choices"];
|
|
15241
|
+
const delta = choices?.[0]?.["delta"];
|
|
15242
|
+
const content = delta?.["content"];
|
|
15243
|
+
if (content)
|
|
15244
|
+
yield content;
|
|
15245
|
+
} catch {
|
|
15246
|
+
}
|
|
15247
|
+
}
|
|
15248
|
+
}
|
|
15249
|
+
streamCompleted = true;
|
|
15250
|
+
} finally {
|
|
15251
|
+
reader.releaseLock();
|
|
15252
|
+
if (streamCompleted)
|
|
15253
|
+
this.rateLimiter.recordSuccess();
|
|
15254
|
+
}
|
|
15255
|
+
}
|
|
15256
|
+
};
|
|
15257
|
+
|
|
15258
|
+
// src/llm/anthropic.ts
|
|
15259
|
+
import fs4 from "node:fs";
|
|
15260
|
+
import os2 from "node:os";
|
|
15261
|
+
import path6 from "node:path";
|
|
15262
|
+
var DEFAULT_MODEL2 = "claude-sonnet-4-20250514";
|
|
15263
|
+
var BASE_URL2 = "https://api.anthropic.com/v1/messages";
|
|
15264
|
+
var ANTHROPIC_VERSION = "2023-06-01";
|
|
15265
|
+
var AnthropicClient = class _AnthropicClient {
|
|
15266
|
+
/** The resolved API key. */
|
|
15267
|
+
apiKey;
|
|
15268
|
+
/** The default model used when none is specified per-call. */
|
|
15269
|
+
defaultModel;
|
|
15270
|
+
timeoutMs;
|
|
15271
|
+
rateLimiter;
|
|
15272
|
+
constructor(opts) {
|
|
15273
|
+
this.defaultModel = opts?.defaultModel ?? process.env["ANTHROPIC_DEFAULT_MODEL"] ?? DEFAULT_MODEL2;
|
|
15274
|
+
const envKey = process.env["ANTHROPIC_API_KEY"]?.trim();
|
|
15275
|
+
const resolved = opts?.apiKey ?? envKey ?? _AnthropicClient.loadKeyFile();
|
|
15276
|
+
if (!resolved) {
|
|
15277
|
+
throw new LlmAuthenticationError(
|
|
15278
|
+
"No Anthropic API key. Set ANTHROPIC_API_KEY, pass apiKey, or create ~/.anthropic"
|
|
15279
|
+
);
|
|
15280
|
+
}
|
|
15281
|
+
this.apiKey = resolved;
|
|
15282
|
+
this.timeoutMs = opts?.timeoutMs ?? 3e5;
|
|
15283
|
+
this.rateLimiter = getGlobalRateLimiter();
|
|
15284
|
+
}
|
|
15285
|
+
/**
|
|
15286
|
+
* Load API key from ~/.anthropic file.
|
|
15287
|
+
*
|
|
15288
|
+
* @returns The key string, or null if the file does not exist.
|
|
15289
|
+
*/
|
|
15290
|
+
static loadKeyFile() {
|
|
15291
|
+
try {
|
|
15292
|
+
const keyPath = path6.join(os2.homedir(), ".anthropic");
|
|
15293
|
+
if (fs4.existsSync(keyPath)) {
|
|
15294
|
+
return fs4.readFileSync(keyPath, "utf-8").trim();
|
|
15295
|
+
}
|
|
15296
|
+
} catch {
|
|
15297
|
+
}
|
|
15298
|
+
return null;
|
|
15299
|
+
}
|
|
15300
|
+
// -- Helpers -------------------------------------------------------------
|
|
15301
|
+
estimateTokens(prompt, systemPrompt) {
|
|
15302
|
+
let total = prompt.length;
|
|
15303
|
+
if (systemPrompt)
|
|
15304
|
+
total += systemPrompt.length;
|
|
15305
|
+
return Math.max(Math.floor(total / 4), 100);
|
|
15306
|
+
}
|
|
15307
|
+
/**
|
|
15308
|
+
* Map an HTTP error response to the appropriate LlmError subclass.
|
|
15309
|
+
*
|
|
15310
|
+
* @param status - HTTP status code.
|
|
15311
|
+
* @param body - Parsed response body (if available).
|
|
15312
|
+
* @param message - Raw error message.
|
|
15313
|
+
*/
|
|
15314
|
+
mapError(status, body, message) {
|
|
15315
|
+
if (status === 401 || status === 403) {
|
|
15316
|
+
return new LlmAuthenticationError(
|
|
15317
|
+
`Anthropic auth failed (${status}): ${message}`
|
|
15318
|
+
);
|
|
15319
|
+
}
|
|
15320
|
+
if (status === 429) {
|
|
15321
|
+
this.rateLimiter.recordRateLimit();
|
|
15322
|
+
return new LlmRateLimitError(
|
|
15323
|
+
`Anthropic rate limit exceeded: ${message}`
|
|
15324
|
+
);
|
|
15325
|
+
}
|
|
15326
|
+
if (status === 408 || status === 504) {
|
|
15327
|
+
return new LlmTimeoutError(
|
|
15328
|
+
`Anthropic request timed out (${status}): ${message}`
|
|
15329
|
+
);
|
|
15330
|
+
}
|
|
15331
|
+
const errorType = body?.["error"];
|
|
15332
|
+
const errType = errorType?.["type"];
|
|
15333
|
+
if (errType === "invalid_request_error") {
|
|
15334
|
+
const msg = message.toLowerCase();
|
|
15335
|
+
if (msg.includes("context") || msg.includes("token")) {
|
|
15336
|
+
return new LlmContextLengthError(
|
|
15337
|
+
`Anthropic context length exceeded: ${message}`
|
|
15338
|
+
);
|
|
15339
|
+
}
|
|
15340
|
+
}
|
|
15341
|
+
const lowerMsg = message.toLowerCase();
|
|
15342
|
+
if (lowerMsg.includes("credit balance") || lowerMsg.includes("billing")) {
|
|
15343
|
+
return new LlmAuthenticationError(
|
|
15344
|
+
`Anthropic billing error: ${message}`
|
|
15345
|
+
);
|
|
15346
|
+
}
|
|
15347
|
+
return new LlmError(`Anthropic API error (${status}): ${message}`);
|
|
15348
|
+
}
|
|
15349
|
+
// -- Public API ----------------------------------------------------------
|
|
15350
|
+
/**
|
|
15351
|
+
* Send a query to Anthropic and get a complete response.
|
|
15352
|
+
*
|
|
15353
|
+
* System prompts are sent via the top-level `system` parameter rather
|
|
15354
|
+
* than as a system message in the messages array, per the Anthropic API
|
|
15355
|
+
* specification.
|
|
15356
|
+
*
|
|
15357
|
+
* @param prompt - The user prompt/query.
|
|
15358
|
+
* @param opts - Optional query configuration.
|
|
15359
|
+
* @returns LlmResponse with generated content and metadata.
|
|
15360
|
+
*/
|
|
15361
|
+
async query(prompt, opts) {
|
|
15362
|
+
const useModel = opts?.model ?? this.defaultModel;
|
|
15363
|
+
const maxTokens = opts?.maxTokens ?? 4096;
|
|
15364
|
+
const temperature = opts?.temperature ?? 1;
|
|
15365
|
+
const systemPrompt = opts?.systemPrompt;
|
|
15366
|
+
const estimated = this.estimateTokens(prompt, systemPrompt);
|
|
15367
|
+
await this.rateLimiter.acquire(estimated);
|
|
15368
|
+
const requestBody = {
|
|
15369
|
+
model: useModel,
|
|
15370
|
+
messages: [{ role: "user", content: prompt }],
|
|
15371
|
+
max_tokens: maxTokens,
|
|
15372
|
+
temperature
|
|
15373
|
+
};
|
|
15374
|
+
if (systemPrompt) {
|
|
15375
|
+
requestBody["system"] = systemPrompt;
|
|
15376
|
+
}
|
|
15377
|
+
let response;
|
|
15378
|
+
try {
|
|
15379
|
+
const controller = new AbortController();
|
|
15380
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
15381
|
+
response = await fetch(BASE_URL2, {
|
|
15382
|
+
method: "POST",
|
|
15383
|
+
headers: {
|
|
15384
|
+
"Content-Type": "application/json",
|
|
15385
|
+
"x-api-key": this.apiKey,
|
|
15386
|
+
"anthropic-version": ANTHROPIC_VERSION
|
|
15387
|
+
},
|
|
15388
|
+
body: JSON.stringify(requestBody),
|
|
15389
|
+
signal: controller.signal
|
|
15390
|
+
});
|
|
15391
|
+
clearTimeout(timer);
|
|
15392
|
+
} catch (err) {
|
|
15393
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
15394
|
+
throw new LlmTimeoutError(
|
|
15395
|
+
`Anthropic request timed out after ${this.timeoutMs}ms`
|
|
15396
|
+
);
|
|
15397
|
+
}
|
|
15398
|
+
throw new LlmError(`Anthropic network error: ${String(err)}`);
|
|
15399
|
+
}
|
|
15400
|
+
if (!response.ok) {
|
|
15401
|
+
let errorBody = null;
|
|
15402
|
+
let errorMessage = response.statusText;
|
|
15403
|
+
try {
|
|
15404
|
+
errorBody = await response.json();
|
|
15405
|
+
const errorObj = errorBody?.["error"];
|
|
15406
|
+
errorMessage = errorObj?.["message"] ?? errorMessage;
|
|
15407
|
+
} catch {
|
|
15408
|
+
}
|
|
15409
|
+
throw this.mapError(response.status, errorBody, errorMessage);
|
|
15410
|
+
}
|
|
15411
|
+
const data = await response.json();
|
|
15412
|
+
this.rateLimiter.recordSuccess();
|
|
15413
|
+
const contentBlocks = data["content"];
|
|
15414
|
+
const textParts = [];
|
|
15415
|
+
if (contentBlocks) {
|
|
15416
|
+
for (const block of contentBlocks) {
|
|
15417
|
+
if (block["type"] === "text" && typeof block["text"] === "string") {
|
|
15418
|
+
textParts.push(block["text"]);
|
|
15419
|
+
}
|
|
15420
|
+
}
|
|
15421
|
+
}
|
|
15422
|
+
const content = textParts.join("").trim();
|
|
15423
|
+
const usageObj = data["usage"];
|
|
15424
|
+
const inputTokens = usageObj?.["input_tokens"] ?? 0;
|
|
15425
|
+
const outputTokens = usageObj?.["output_tokens"] ?? 0;
|
|
15426
|
+
return {
|
|
15427
|
+
content,
|
|
15428
|
+
model: data["model"] ?? useModel,
|
|
15429
|
+
usage: {
|
|
15430
|
+
input_tokens: inputTokens,
|
|
15431
|
+
output_tokens: outputTokens
|
|
15432
|
+
}
|
|
15433
|
+
};
|
|
15434
|
+
}
|
|
15435
|
+
/**
|
|
15436
|
+
* Stream a query response chunk by chunk.
|
|
15437
|
+
*
|
|
15438
|
+
* Uses Server-Sent Events (SSE) streaming from the Anthropic API.
|
|
15439
|
+
* System prompts are sent via the top-level `system` parameter.
|
|
15440
|
+
*
|
|
15441
|
+
* @param prompt - The user prompt/query.
|
|
15442
|
+
* @param opts - Optional query configuration.
|
|
15443
|
+
* @yields Text chunks as they arrive from the API.
|
|
15444
|
+
*/
|
|
15445
|
+
async *streamQuery(prompt, opts) {
|
|
15446
|
+
const useModel = opts?.model ?? this.defaultModel;
|
|
15447
|
+
const maxTokens = opts?.maxTokens ?? 4096;
|
|
15448
|
+
const temperature = opts?.temperature ?? 1;
|
|
15449
|
+
const systemPrompt = opts?.systemPrompt;
|
|
15450
|
+
const estimated = this.estimateTokens(prompt, systemPrompt);
|
|
15451
|
+
await this.rateLimiter.acquire(estimated);
|
|
15452
|
+
const requestBody = {
|
|
15453
|
+
model: useModel,
|
|
15454
|
+
messages: [{ role: "user", content: prompt }],
|
|
15455
|
+
max_tokens: maxTokens,
|
|
15456
|
+
temperature,
|
|
15457
|
+
stream: true
|
|
15458
|
+
};
|
|
15459
|
+
if (systemPrompt) {
|
|
15460
|
+
requestBody["system"] = systemPrompt;
|
|
15461
|
+
}
|
|
15462
|
+
let response;
|
|
15463
|
+
try {
|
|
15464
|
+
const controller = new AbortController();
|
|
15465
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
15466
|
+
response = await fetch(BASE_URL2, {
|
|
15467
|
+
method: "POST",
|
|
15468
|
+
headers: {
|
|
15469
|
+
"Content-Type": "application/json",
|
|
15470
|
+
"x-api-key": this.apiKey,
|
|
15471
|
+
"anthropic-version": ANTHROPIC_VERSION
|
|
15472
|
+
},
|
|
15473
|
+
body: JSON.stringify(requestBody),
|
|
15474
|
+
signal: controller.signal
|
|
15475
|
+
});
|
|
15476
|
+
clearTimeout(timer);
|
|
15477
|
+
} catch (err) {
|
|
15478
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
15479
|
+
throw new LlmTimeoutError(
|
|
15480
|
+
`Anthropic request timed out after ${this.timeoutMs}ms`
|
|
15481
|
+
);
|
|
15482
|
+
}
|
|
15483
|
+
throw new LlmError(`Anthropic network error: ${String(err)}`);
|
|
15484
|
+
}
|
|
15485
|
+
if (!response.ok) {
|
|
15486
|
+
let errorBody = null;
|
|
15487
|
+
let errorMessage = response.statusText;
|
|
15488
|
+
try {
|
|
15489
|
+
errorBody = await response.json();
|
|
15490
|
+
const errorObj = errorBody?.["error"];
|
|
15491
|
+
errorMessage = errorObj?.["message"] ?? errorMessage;
|
|
15492
|
+
} catch {
|
|
15493
|
+
}
|
|
15494
|
+
throw this.mapError(response.status, errorBody, errorMessage);
|
|
15495
|
+
}
|
|
15496
|
+
if (!response.body) {
|
|
15497
|
+
throw new LlmError("Anthropic streaming response has no body");
|
|
15498
|
+
}
|
|
15499
|
+
const reader = response.body.getReader();
|
|
15500
|
+
const decoder = new TextDecoder();
|
|
15501
|
+
let buffer = "";
|
|
15502
|
+
let streamCompleted = false;
|
|
15503
|
+
try {
|
|
15504
|
+
for (; ; ) {
|
|
15505
|
+
const { done, value } = await reader.read();
|
|
15506
|
+
if (done)
|
|
15507
|
+
break;
|
|
15508
|
+
buffer += decoder.decode(value, { stream: true });
|
|
15509
|
+
const lines = buffer.split("\n");
|
|
15510
|
+
buffer = lines.pop() ?? "";
|
|
15511
|
+
for (const line of lines) {
|
|
15512
|
+
const trimmed = line.trim();
|
|
15513
|
+
if (!trimmed || !trimmed.startsWith("data: "))
|
|
15514
|
+
continue;
|
|
15515
|
+
const payload = trimmed.slice(6);
|
|
15516
|
+
try {
|
|
15517
|
+
const event = JSON.parse(payload);
|
|
15518
|
+
const eventType = event["type"];
|
|
15519
|
+
if (eventType === "content_block_delta") {
|
|
15520
|
+
const delta = event["delta"];
|
|
15521
|
+
if (delta?.["type"] === "text_delta") {
|
|
15522
|
+
const text = delta["text"];
|
|
15523
|
+
if (text)
|
|
15524
|
+
yield text;
|
|
15525
|
+
}
|
|
15526
|
+
} else if (eventType === "message_stop") {
|
|
15527
|
+
streamCompleted = true;
|
|
15528
|
+
return;
|
|
15529
|
+
}
|
|
15530
|
+
} catch {
|
|
15531
|
+
}
|
|
15532
|
+
}
|
|
15533
|
+
}
|
|
15534
|
+
streamCompleted = true;
|
|
15535
|
+
} finally {
|
|
15536
|
+
reader.releaseLock();
|
|
15537
|
+
if (streamCompleted)
|
|
15538
|
+
this.rateLimiter.recordSuccess();
|
|
15539
|
+
}
|
|
15540
|
+
}
|
|
15541
|
+
};
|
|
15542
|
+
|
|
15543
|
+
// src/llm/factory.ts
|
|
15544
|
+
function createLlmClient(opts) {
|
|
15545
|
+
const clientType = (opts?.clientType ?? process.env["LLM_CLIENT_TYPE"] ?? "mock").toLowerCase();
|
|
15546
|
+
const model = opts?.model ?? process.env["LLM_DEFAULT_MODEL"];
|
|
15547
|
+
if (clientType === "mock") {
|
|
15548
|
+
return new MockLlmClient({
|
|
15549
|
+
defaultModel: model ?? "mock-model"
|
|
15550
|
+
});
|
|
15551
|
+
}
|
|
15552
|
+
if (clientType === "groq") {
|
|
15553
|
+
return new GroqClient({
|
|
15554
|
+
apiKey: opts?.apiKey,
|
|
15555
|
+
defaultModel: model,
|
|
15556
|
+
timeoutMs: opts?.timeoutMs
|
|
15557
|
+
});
|
|
15558
|
+
}
|
|
15559
|
+
if (clientType === "anthropic") {
|
|
15560
|
+
return new AnthropicClient({
|
|
15561
|
+
apiKey: opts?.apiKey,
|
|
15562
|
+
defaultModel: model,
|
|
15563
|
+
timeoutMs: opts?.timeoutMs
|
|
15564
|
+
});
|
|
15565
|
+
}
|
|
15566
|
+
throw new Error(
|
|
15567
|
+
`Unknown LLM client type: "${clientType}". Valid types: "groq", "anthropic", "mock"`
|
|
15568
|
+
);
|
|
15569
|
+
}
|
|
15570
|
+
|
|
14156
15571
|
// src/index.ts
|
|
14157
|
-
var version = "0.
|
|
15572
|
+
var version = "0.6.0";
|
|
14158
15573
|
export {
|
|
14159
15574
|
ACKLifecycleManager,
|
|
15575
|
+
AGENT_STATE_FAILED,
|
|
15576
|
+
AGENT_STATE_INITIALIZING,
|
|
15577
|
+
AGENT_STATE_RUNNING,
|
|
15578
|
+
AGENT_STATE_SHUTTING_DOWN,
|
|
14160
15579
|
AckStage2 as AckStage,
|
|
14161
15580
|
ActivityBuffer,
|
|
14162
15581
|
ActivityBufferSync,
|
|
14163
15582
|
ActivityClient,
|
|
14164
15583
|
AgentState,
|
|
14165
15584
|
AgentStateMachine,
|
|
15585
|
+
AnthropicClient,
|
|
14166
15586
|
ArtifactType,
|
|
14167
15587
|
BaseClient,
|
|
14168
15588
|
BordaCountAggregator,
|
|
14169
15589
|
BufferFullError,
|
|
14170
15590
|
CT_AGENT_REPORT_V1,
|
|
14171
15591
|
CT_SCHEDULER_COMMAND_V1,
|
|
15592
|
+
CancellationManager,
|
|
15593
|
+
CancellationValidationError,
|
|
14172
15594
|
CommunicationClass,
|
|
14173
15595
|
ConfidenceWeightedAggregator,
|
|
14174
15596
|
ConnectorClient,
|
|
14175
15597
|
DEDUP_WINDOW_S,
|
|
14176
15598
|
DEFAULT_ACK_TIMEOUT_MS,
|
|
15599
|
+
DEFAULT_BACKOFF_MULTIPLIER,
|
|
15600
|
+
DEFAULT_EFFECTIVE_MAX_REDIRECTS,
|
|
14177
15601
|
DEFAULT_ESCALATION_POLICY,
|
|
14178
15602
|
DEFAULT_EXECUTION_POLICY,
|
|
15603
|
+
DEFAULT_INITIAL_BACKOFF_MS,
|
|
15604
|
+
DEFAULT_MAX_BACKOFF_MS,
|
|
15605
|
+
DEFAULT_MAX_REDIRECTS,
|
|
15606
|
+
DEFAULT_MAX_RETRIES_ON_OVERLOADED,
|
|
14179
15607
|
DEFAULT_NEGOTIATION_POLICY,
|
|
15608
|
+
DEFAULT_PEER_LIVENESS_THRESHOLD_MS,
|
|
14180
15609
|
DebateIntensity,
|
|
14181
15610
|
DecisionOutcome,
|
|
14182
15611
|
DefaultWorktreePolicy,
|
|
@@ -14185,6 +15614,9 @@ export {
|
|
|
14185
15614
|
ErrorCode,
|
|
14186
15615
|
ErrorCodeMapper,
|
|
14187
15616
|
FileBackend,
|
|
15617
|
+
GatewayRedirectEmitter,
|
|
15618
|
+
GatewayValidationError,
|
|
15619
|
+
GroqClient,
|
|
14188
15620
|
HITLClient,
|
|
14189
15621
|
HandoffClient,
|
|
14190
15622
|
HandoffStatus,
|
|
@@ -14199,11 +15631,19 @@ export {
|
|
|
14199
15631
|
JSONFilePersistence,
|
|
14200
15632
|
JsonFilePolicyStore,
|
|
14201
15633
|
KeyringBackend,
|
|
15634
|
+
LlmAuthenticationError,
|
|
15635
|
+
LlmContextLengthError,
|
|
15636
|
+
LlmError,
|
|
15637
|
+
LlmRateLimitError,
|
|
15638
|
+
LlmTimeoutError,
|
|
14202
15639
|
LoggingClient,
|
|
15640
|
+
MIN_GRACE_PERIOD_MS,
|
|
14203
15641
|
MajorityVoteAggregator,
|
|
14204
15642
|
MaxEntriesStrategy,
|
|
14205
15643
|
MessageProcessor,
|
|
14206
15644
|
MessageType,
|
|
15645
|
+
MockLlmClient,
|
|
15646
|
+
NON_SERVING_AGENT_STATES,
|
|
14207
15647
|
NegotiationClient,
|
|
14208
15648
|
NegotiationError,
|
|
14209
15649
|
NegotiationRoomClient,
|
|
@@ -14212,12 +15652,16 @@ export {
|
|
|
14212
15652
|
NegotiationValidationError,
|
|
14213
15653
|
NoOpAuditor,
|
|
14214
15654
|
NodeStatus,
|
|
15655
|
+
PeerSelector,
|
|
14215
15656
|
PermissionError,
|
|
14216
15657
|
PersistentActivityBuffer,
|
|
14217
15658
|
PersistentWorktreeState,
|
|
14218
15659
|
PolicyStoreError,
|
|
14219
15660
|
PolicyViolationError,
|
|
14220
15661
|
PreemptionError,
|
|
15662
|
+
REGISTRATION_TYPE_STANDARD_AGENT,
|
|
15663
|
+
REGISTRATION_TYPE_SWARM_GATEWAY,
|
|
15664
|
+
RETRY_AFTER_JITTER_RATIO,
|
|
14221
15665
|
ReasoningClient,
|
|
14222
15666
|
RegistryClient,
|
|
14223
15667
|
Resolver,
|
|
@@ -14240,6 +15684,7 @@ export {
|
|
|
14240
15684
|
StateTransitionError,
|
|
14241
15685
|
Sw4rmError,
|
|
14242
15686
|
TimeoutError,
|
|
15687
|
+
TokenBucket,
|
|
14243
15688
|
ToolClient,
|
|
14244
15689
|
TriggerType,
|
|
14245
15690
|
ValidationError,
|
|
@@ -14255,8 +15700,10 @@ export {
|
|
|
14255
15700
|
aggregateVotes,
|
|
14256
15701
|
buildAckEnvelope,
|
|
14257
15702
|
buildEnvelope,
|
|
15703
|
+
buildRateLimiterConfig,
|
|
14258
15704
|
computeEnvelopeHash,
|
|
14259
15705
|
computeIdempotencyToken,
|
|
15706
|
+
createLlmClient,
|
|
14260
15707
|
createResilientIncomingStream,
|
|
14261
15708
|
createSimpleProof,
|
|
14262
15709
|
decodeAgentReportV1,
|
|
@@ -14265,10 +15712,12 @@ export {
|
|
|
14265
15712
|
defaultEndpoints,
|
|
14266
15713
|
defaultRetryPolicy,
|
|
14267
15714
|
defaultSW4RMConfig,
|
|
15715
|
+
delegateToSwarm,
|
|
14268
15716
|
durationToMs,
|
|
14269
15717
|
encodeSchedulerCommandV1,
|
|
14270
15718
|
getConfig,
|
|
14271
15719
|
getDefaultStore,
|
|
15720
|
+
getGlobalRateLimiter,
|
|
14272
15721
|
getValidTransitions,
|
|
14273
15722
|
isTerminalEnvelopeState,
|
|
14274
15723
|
isValidTransition,
|
|
@@ -14283,6 +15732,7 @@ export {
|
|
|
14283
15732
|
parseNegotiationEvent,
|
|
14284
15733
|
resetConfig,
|
|
14285
15734
|
resetDefaultStore,
|
|
15735
|
+
resetGlobalRateLimiter,
|
|
14286
15736
|
selectBackend,
|
|
14287
15737
|
sendMessageWithAck,
|
|
14288
15738
|
setConfig,
|