@cadraos/sdk 0.3.12 → 0.3.13
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/chat/index.cjs +524 -46
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.js +524 -46
- package/dist/chat/index.js.map +1 -1
- package/dist/index.cjs +453 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -1
- package/dist/index.d.ts +54 -1
- package/dist/index.js +453 -38
- package/dist/index.js.map +1 -1
- package/dist/kanban/engine/index.cjs +30 -8
- package/dist/kanban/engine/index.cjs.map +1 -1
- package/dist/kanban/engine/index.js +30 -8
- package/dist/kanban/engine/index.js.map +1 -1
- package/dist/kanban/index.cjs +453 -38
- package/dist/kanban/index.cjs.map +1 -1
- package/dist/kanban/index.js +453 -38
- package/dist/kanban/index.js.map +1 -1
- package/dist/teams/index.cjs +453 -38
- package/dist/teams/index.cjs.map +1 -1
- package/dist/teams/index.js +453 -38
- package/dist/teams/index.js.map +1 -1
- package/package.json +1 -1
package/dist/kanban/index.cjs
CHANGED
|
@@ -3895,7 +3895,8 @@ var SSEClient = class {
|
|
|
3895
3895
|
"Authorization": this.config.authHeader,
|
|
3896
3896
|
"X-Org-Id": String(this.config.orgId),
|
|
3897
3897
|
"Accept": "text/event-stream",
|
|
3898
|
-
"Cache-Control": "no-cache"
|
|
3898
|
+
"Cache-Control": "no-cache",
|
|
3899
|
+
...this.config.lastEventId ? { "Last-Event-ID": this.config.lastEventId } : {}
|
|
3899
3900
|
},
|
|
3900
3901
|
signal: this.abortController.signal
|
|
3901
3902
|
});
|
|
@@ -4111,13 +4112,167 @@ function normalizeUrl(url) {
|
|
|
4111
4112
|
return url;
|
|
4112
4113
|
}
|
|
4113
4114
|
|
|
4115
|
+
// src/streaming/poll-policy.ts
|
|
4116
|
+
var UNNAMED_RATE_LIMIT_WAIT_MS = 6e4;
|
|
4117
|
+
function asDict(value) {
|
|
4118
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
4119
|
+
}
|
|
4120
|
+
var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
|
|
4121
|
+
"rate_limit_error",
|
|
4122
|
+
"rate_limited",
|
|
4123
|
+
"rate_limit_exceeded",
|
|
4124
|
+
"too_many_requests"
|
|
4125
|
+
]);
|
|
4126
|
+
function isRateLimited(error) {
|
|
4127
|
+
if (typeof error === "string") return /\b429\b/.test(error);
|
|
4128
|
+
const err = asDict(error);
|
|
4129
|
+
if (!err) return false;
|
|
4130
|
+
for (const key of ["status", "statusCode", "httpStatus", "code"]) {
|
|
4131
|
+
if (err[key] === 429 || err[key] === "429") return true;
|
|
4132
|
+
}
|
|
4133
|
+
const response = asDict(err.response);
|
|
4134
|
+
if (response && (response.status === 429 || response.statusCode === 429)) return true;
|
|
4135
|
+
if (typeof err.code === "string" && RATE_LIMIT_CODES.has(err.code.toLowerCase())) return true;
|
|
4136
|
+
if (err.name === "RateLimitError") return true;
|
|
4137
|
+
const message = typeof err.message === "string" ? err.message : "";
|
|
4138
|
+
return /\b429\b/.test(message) || /rate[\s_-]?limit/i.test(message) || /too many requests/i.test(message);
|
|
4139
|
+
}
|
|
4140
|
+
function readHeader(bag, name) {
|
|
4141
|
+
if (bag === null || bag === void 0) return null;
|
|
4142
|
+
const getter = bag.get;
|
|
4143
|
+
if (typeof getter === "function") {
|
|
4144
|
+
try {
|
|
4145
|
+
const value = getter.call(bag, name);
|
|
4146
|
+
if (value !== null && value !== void 0) return String(value);
|
|
4147
|
+
} catch {
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
const dict = asDict(bag);
|
|
4151
|
+
if (!dict) return null;
|
|
4152
|
+
const wanted = name.toLowerCase();
|
|
4153
|
+
for (const [key, value] of Object.entries(dict)) {
|
|
4154
|
+
if (key.toLowerCase() === wanted && value !== null && value !== void 0) {
|
|
4155
|
+
return String(value);
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
return null;
|
|
4159
|
+
}
|
|
4160
|
+
function parseRetryAfter(raw, now) {
|
|
4161
|
+
if (raw === null || raw === void 0) return null;
|
|
4162
|
+
if (typeof raw === "number") {
|
|
4163
|
+
return Number.isFinite(raw) && raw >= 0 ? raw * 1e3 : null;
|
|
4164
|
+
}
|
|
4165
|
+
if (typeof raw !== "string") return null;
|
|
4166
|
+
const trimmed = raw.trim();
|
|
4167
|
+
if (trimmed === "") return null;
|
|
4168
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
4169
|
+
const at = Date.parse(trimmed);
|
|
4170
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
4171
|
+
}
|
|
4172
|
+
function parseResetAt(raw, now) {
|
|
4173
|
+
if (raw === null || raw === void 0) return null;
|
|
4174
|
+
const numeric = typeof raw === "number" ? raw : typeof raw === "string" && /^\d+(\.\d+)?$/.test(raw.trim()) ? Number(raw.trim()) : null;
|
|
4175
|
+
if (numeric !== null) {
|
|
4176
|
+
if (!Number.isFinite(numeric) || numeric < 0) return null;
|
|
4177
|
+
if (numeric > 1e11) return Math.max(0, numeric - now);
|
|
4178
|
+
if (numeric > 1e9) return Math.max(0, numeric * 1e3 - now);
|
|
4179
|
+
return numeric * 1e3;
|
|
4180
|
+
}
|
|
4181
|
+
if (typeof raw !== "string") return null;
|
|
4182
|
+
const at = Date.parse(raw.trim());
|
|
4183
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
4184
|
+
}
|
|
4185
|
+
function getRetryAfterMs(error, now = Date.now()) {
|
|
4186
|
+
const err = asDict(error);
|
|
4187
|
+
if (!err) return null;
|
|
4188
|
+
const details = asDict(err.details);
|
|
4189
|
+
const response = asDict(err.response);
|
|
4190
|
+
const body = asDict(err.body) ?? asDict(response?.data);
|
|
4191
|
+
const bodyError = asDict(body?.error);
|
|
4192
|
+
const bodyDetails = asDict(bodyError?.details) ?? asDict(body?.details);
|
|
4193
|
+
for (const source of [err, details, bodyDetails]) {
|
|
4194
|
+
const ms = source?.retryAfterMs;
|
|
4195
|
+
if (typeof ms === "number" && Number.isFinite(ms) && ms >= 0) return ms;
|
|
4196
|
+
}
|
|
4197
|
+
for (const source of [err, details, bodyDetails, bodyError]) {
|
|
4198
|
+
const parsed = parseRetryAfter(source?.retryAfter, now);
|
|
4199
|
+
if (parsed !== null) return parsed;
|
|
4200
|
+
}
|
|
4201
|
+
const headerBags = [err.headers, response?.headers, details?.headers, body?.headers];
|
|
4202
|
+
for (const bag of headerBags) {
|
|
4203
|
+
const parsed = parseRetryAfter(readHeader(bag, "retry-after"), now);
|
|
4204
|
+
if (parsed !== null) return parsed;
|
|
4205
|
+
}
|
|
4206
|
+
for (const source of [details, bodyDetails, err]) {
|
|
4207
|
+
for (const key of ["resetAt", "reset", "rateLimitReset"]) {
|
|
4208
|
+
const parsed = parseResetAt(source?.[key], now);
|
|
4209
|
+
if (parsed !== null) return parsed;
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
for (const bag of headerBags) {
|
|
4213
|
+
for (const header of ["x-ratelimit-reset", "ratelimit-reset"]) {
|
|
4214
|
+
const parsed = parseResetAt(readHeader(bag, header), now);
|
|
4215
|
+
if (parsed !== null) return parsed;
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
return null;
|
|
4219
|
+
}
|
|
4220
|
+
function getHttpStatus(error) {
|
|
4221
|
+
const err = asDict(error);
|
|
4222
|
+
if (!err) return null;
|
|
4223
|
+
for (const key of ["status", "statusCode", "httpStatus"]) {
|
|
4224
|
+
const value = err[key];
|
|
4225
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4226
|
+
if (typeof value === "string" && /^\d{3}$/.test(value)) return Number(value);
|
|
4227
|
+
}
|
|
4228
|
+
const response = asDict(err.response);
|
|
4229
|
+
for (const key of ["status", "statusCode"]) {
|
|
4230
|
+
const value = response?.[key];
|
|
4231
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4232
|
+
}
|
|
4233
|
+
return null;
|
|
4234
|
+
}
|
|
4235
|
+
function isTerminalClientError(error) {
|
|
4236
|
+
const status = getHttpStatus(error);
|
|
4237
|
+
if (status === null) return false;
|
|
4238
|
+
return status >= 400 && status < 500 && status !== 429 && status !== 408;
|
|
4239
|
+
}
|
|
4240
|
+
var FAILURE_GROWTH_FACTOR = 2;
|
|
4241
|
+
var IDLE_GROWTH_FACTOR = 1.5;
|
|
4242
|
+
function nextFailureDelayMs(currentMs, maxMs) {
|
|
4243
|
+
return Math.min(Math.max(currentMs, 1) * FAILURE_GROWTH_FACTOR, maxMs);
|
|
4244
|
+
}
|
|
4245
|
+
function nextIdleDelayMs(currentMs, maxMs) {
|
|
4246
|
+
return Math.min(Math.ceil(Math.max(currentMs, 1) * IDLE_GROWTH_FACTOR), maxMs);
|
|
4247
|
+
}
|
|
4248
|
+
function applyJitter(delayMs, ratio, random = Math.random) {
|
|
4249
|
+
if (ratio <= 0) return delayMs;
|
|
4250
|
+
return Math.round(delayMs * (1 + random() * ratio));
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4114
4253
|
// src/streaming/stream-controller.ts
|
|
4115
4254
|
var DEFAULT_STREAMING_CONFIG = {
|
|
4116
4255
|
maxReconnectAttempts: 5,
|
|
4117
4256
|
reconnectBaseDelayMs: 1e3,
|
|
4118
4257
|
reconnectMaxDelayMs: 1e4,
|
|
4258
|
+
// NOTE (unchanged, deliberately): this watchdog force-disconnects a HEALTHY
|
|
4259
|
+
// SSE connection after 30s of silence and routes it through the same
|
|
4260
|
+
// handleConnectionError path as a real transport failure. Ordinary LLM
|
|
4261
|
+
// latency exceeds 30s routinely, so it fires against working streams — that
|
|
4262
|
+
// is what drives sessions into the polling fallback in the first place. The
|
|
4263
|
+
// fix is a server-side SSE heartbeat, not a bigger number here; raising this
|
|
4264
|
+
// is a guess against a moving target. Everything below only bounds what the
|
|
4265
|
+
// fallback costs once it has been entered.
|
|
4119
4266
|
activityTimeoutMs: 3e4,
|
|
4120
|
-
pollingIntervalMs:
|
|
4267
|
+
pollingIntervalMs: 5e3,
|
|
4268
|
+
pollingMaxIntervalMs: 3e4,
|
|
4269
|
+
pollingMaxConsecutiveFailures: 10,
|
|
4270
|
+
pollingMaxConsecutiveRateLimits: 3,
|
|
4271
|
+
pollingMaxConsecutiveClientErrors: 3,
|
|
4272
|
+
pollingMaxDurationMs: 18e5,
|
|
4273
|
+
pollingMaxRetryAfterMs: 3e5,
|
|
4274
|
+
pollingJitterRatio: 0.1,
|
|
4275
|
+
sseRetryIntervalMs: 6e4
|
|
4121
4276
|
};
|
|
4122
4277
|
var DEFAULT_POLLING_ENABLED = true;
|
|
4123
4278
|
var ExecutionStreamControllerImpl = class {
|
|
@@ -4130,9 +4285,24 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4130
4285
|
seenEventIds = /* @__PURE__ */ new Set();
|
|
4131
4286
|
_lastEventId = null;
|
|
4132
4287
|
activityTimeoutId = null;
|
|
4133
|
-
pollingIntervalId = null;
|
|
4134
4288
|
finalExecution = null;
|
|
4135
4289
|
finalError = null;
|
|
4290
|
+
// --- Fallback polling state ---
|
|
4291
|
+
// A self-rescheduling timeout, not setInterval: the delay has to change
|
|
4292
|
+
// between ticks (that is what backoff IS), and a timeout chain also cannot
|
|
4293
|
+
// stack overlapping requests the way a fixed interval does when the API is
|
|
4294
|
+
// slow to answer.
|
|
4295
|
+
pollTimeoutId = null;
|
|
4296
|
+
pollDelayMs = 0;
|
|
4297
|
+
pollConsecutiveFailures = 0;
|
|
4298
|
+
pollConsecutiveRateLimits = 0;
|
|
4299
|
+
pollConsecutiveClientErrors = 0;
|
|
4300
|
+
/** When the loop last observed anything new — the clock the give-up bound runs on. */
|
|
4301
|
+
pollLastProgressAt = 0;
|
|
4302
|
+
pollInFlight = false;
|
|
4303
|
+
lastPolledStatus = null;
|
|
4304
|
+
sseRetryTimeoutId = null;
|
|
4305
|
+
sseRetryInFlight = false;
|
|
4136
4306
|
// Event callbacks
|
|
4137
4307
|
listeners = {
|
|
4138
4308
|
log: [],
|
|
@@ -4161,6 +4331,39 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4161
4331
|
this.config = { ...DEFAULT_STREAMING_CONFIG, ...options.config };
|
|
4162
4332
|
this.pollingEnabled = options.pollingEnabled ?? DEFAULT_POLLING_ENABLED;
|
|
4163
4333
|
}
|
|
4334
|
+
// Resolved fallback-poll settings. Read through `??` rather than off the
|
|
4335
|
+
// spread config so an explicit `undefined` in a caller's partial config still
|
|
4336
|
+
// lands on the default instead of NaN-ing the arithmetic.
|
|
4337
|
+
get pollBaseIntervalMs() {
|
|
4338
|
+
return this.config.pollingIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingIntervalMs;
|
|
4339
|
+
}
|
|
4340
|
+
get pollMaxIntervalMs() {
|
|
4341
|
+
return Math.max(
|
|
4342
|
+
this.config.pollingMaxIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxIntervalMs,
|
|
4343
|
+
this.pollBaseIntervalMs
|
|
4344
|
+
);
|
|
4345
|
+
}
|
|
4346
|
+
get pollMaxConsecutiveFailures() {
|
|
4347
|
+
return this.config.pollingMaxConsecutiveFailures ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveFailures;
|
|
4348
|
+
}
|
|
4349
|
+
get pollMaxConsecutiveRateLimits() {
|
|
4350
|
+
return this.config.pollingMaxConsecutiveRateLimits ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveRateLimits;
|
|
4351
|
+
}
|
|
4352
|
+
get pollMaxConsecutiveClientErrors() {
|
|
4353
|
+
return this.config.pollingMaxConsecutiveClientErrors ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveClientErrors;
|
|
4354
|
+
}
|
|
4355
|
+
get pollMaxDurationMs() {
|
|
4356
|
+
return this.config.pollingMaxDurationMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxDurationMs;
|
|
4357
|
+
}
|
|
4358
|
+
get pollMaxRetryAfterMs() {
|
|
4359
|
+
return this.config.pollingMaxRetryAfterMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxRetryAfterMs;
|
|
4360
|
+
}
|
|
4361
|
+
get pollJitterRatio() {
|
|
4362
|
+
return this.config.pollingJitterRatio ?? DEFAULT_STREAMING_CONFIG.pollingJitterRatio;
|
|
4363
|
+
}
|
|
4364
|
+
get sseRetryIntervalMs() {
|
|
4365
|
+
return this.config.sseRetryIntervalMs ?? DEFAULT_STREAMING_CONFIG.sseRetryIntervalMs;
|
|
4366
|
+
}
|
|
4164
4367
|
/**
|
|
4165
4368
|
* Get the last event ID (for resume support)
|
|
4166
4369
|
*/
|
|
@@ -4251,28 +4454,45 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4251
4454
|
console.log("[SDK:StreamController] executionId:", this.options.executionId);
|
|
4252
4455
|
console.log("[SDK:StreamController] orgId:", this.options.orgId);
|
|
4253
4456
|
console.log("[SDK:StreamController] apiKey:", authToken ? `${authToken.substring(0, 20)}...` : "MISSING");
|
|
4254
|
-
this.sseClient
|
|
4457
|
+
if (this.sseClient) {
|
|
4458
|
+
this.sseClient.disconnect();
|
|
4459
|
+
this.sseClient = null;
|
|
4460
|
+
}
|
|
4461
|
+
const client = new SSEClient({
|
|
4255
4462
|
url,
|
|
4256
4463
|
authHeader: `Bearer ${authToken}`,
|
|
4257
4464
|
orgId: this.options.orgId,
|
|
4258
4465
|
fetch: this.options.fetch,
|
|
4259
|
-
|
|
4466
|
+
// Resume from where this session left off. On a first connect this is
|
|
4467
|
+
// null and the header is omitted.
|
|
4468
|
+
lastEventId: this._lastEventId,
|
|
4469
|
+
onEvent: (event) => {
|
|
4470
|
+
if (this.sseClient !== client) return;
|
|
4471
|
+
this.handleEvent(event);
|
|
4472
|
+
},
|
|
4260
4473
|
onError: (error) => {
|
|
4474
|
+
if (this.sseClient !== client) return;
|
|
4261
4475
|
console.error("[SDK:StreamController] SSE error:", error);
|
|
4262
4476
|
this.handleConnectionError(error);
|
|
4263
4477
|
},
|
|
4264
4478
|
onConnectionChange: (connected) => {
|
|
4479
|
+
if (this.sseClient !== client) return;
|
|
4265
4480
|
console.log("[SDK:StreamController] SSE connection change:", connected);
|
|
4266
4481
|
if (connected) {
|
|
4267
4482
|
this.status = "connected";
|
|
4268
4483
|
this.reconnectAttempts = 0;
|
|
4484
|
+
this.stopPolling();
|
|
4269
4485
|
this.resetActivityTimeout();
|
|
4270
4486
|
}
|
|
4271
|
-
this.emit("connectionChange", {
|
|
4487
|
+
this.emit("connectionChange", {
|
|
4488
|
+
connected,
|
|
4489
|
+
usingFallback: this.status === "fallback_polling"
|
|
4490
|
+
});
|
|
4272
4491
|
}
|
|
4273
4492
|
});
|
|
4493
|
+
this.sseClient = client;
|
|
4274
4494
|
try {
|
|
4275
|
-
await
|
|
4495
|
+
await client.connect();
|
|
4276
4496
|
console.log("[SDK:StreamController] SSE connect() completed");
|
|
4277
4497
|
} catch (error) {
|
|
4278
4498
|
console.error("[SDK:StreamController] SSE connect() threw:", error);
|
|
@@ -4511,6 +4731,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4511
4731
|
handleComplete(data) {
|
|
4512
4732
|
this.status = "closed";
|
|
4513
4733
|
this.stopActivityTimeout();
|
|
4734
|
+
this.stopPolling();
|
|
4514
4735
|
const sessionId = data.sessionId ?? this.options.sessionId;
|
|
4515
4736
|
const execution = {
|
|
4516
4737
|
uuid: this.options.executionId,
|
|
@@ -4537,6 +4758,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4537
4758
|
handleError(error) {
|
|
4538
4759
|
this.status = "closed";
|
|
4539
4760
|
this.stopActivityTimeout();
|
|
4761
|
+
this.stopPolling();
|
|
4540
4762
|
this.finalError = error;
|
|
4541
4763
|
this.emit("error", error);
|
|
4542
4764
|
if (this.waitReject) {
|
|
@@ -4563,6 +4785,10 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4563
4785
|
this.handleError({ error: msg, errorCode: "stream_unrecoverable" });
|
|
4564
4786
|
return;
|
|
4565
4787
|
}
|
|
4788
|
+
if (this.status === "fallback_polling") {
|
|
4789
|
+
this.startPolling();
|
|
4790
|
+
return;
|
|
4791
|
+
}
|
|
4566
4792
|
if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
4567
4793
|
this.status = "reconnecting";
|
|
4568
4794
|
this.reconnectAttempts++;
|
|
@@ -4632,41 +4858,215 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4632
4858
|
}
|
|
4633
4859
|
}
|
|
4634
4860
|
/**
|
|
4635
|
-
* Start polling fallback
|
|
4861
|
+
* Start polling fallback.
|
|
4862
|
+
*
|
|
4863
|
+
* Idempotent — calling it while a loop is already running is a no-op, so no
|
|
4864
|
+
* path can end up with two loops racing the same execution.
|
|
4636
4865
|
*/
|
|
4637
4866
|
startPolling() {
|
|
4638
|
-
this.
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4867
|
+
if (this.pollTimeoutId !== null || this.pollInFlight) {
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
this.pollLastProgressAt = Date.now();
|
|
4871
|
+
this.pollDelayMs = this.pollBaseIntervalMs;
|
|
4872
|
+
this.pollConsecutiveFailures = 0;
|
|
4873
|
+
this.pollConsecutiveRateLimits = 0;
|
|
4874
|
+
this.pollConsecutiveClientErrors = 0;
|
|
4875
|
+
this.lastPolledStatus = null;
|
|
4876
|
+
this.stopActivityTimeout();
|
|
4877
|
+
this.scheduleSSERetry();
|
|
4878
|
+
this.schedulePoll(this.pollDelayMs);
|
|
4879
|
+
}
|
|
4880
|
+
/**
|
|
4881
|
+
* Queue the next poll tick.
|
|
4882
|
+
*/
|
|
4883
|
+
schedulePoll(delayMs) {
|
|
4884
|
+
if (this.status !== "fallback_polling") {
|
|
4885
|
+
return;
|
|
4886
|
+
}
|
|
4887
|
+
this.pollTimeoutId = setTimeout(() => {
|
|
4888
|
+
this.pollTimeoutId = null;
|
|
4889
|
+
void this.runPoll();
|
|
4890
|
+
}, Math.max(0, Math.round(delayMs)));
|
|
4891
|
+
}
|
|
4892
|
+
/**
|
|
4893
|
+
* One poll tick: fetch state, emit what is new, then decide how long to wait
|
|
4894
|
+
* before the next one — or whether there should be a next one at all.
|
|
4895
|
+
*/
|
|
4896
|
+
async runPoll() {
|
|
4897
|
+
if (this.status !== "fallback_polling") {
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4900
|
+
if (Date.now() - this.pollLastProgressAt >= this.pollMaxDurationMs) {
|
|
4901
|
+
this.failPolling(
|
|
4902
|
+
`Streaming fallback gave up: no execution progress for ${Math.round(this.pollMaxDurationMs / 1e3)}s`,
|
|
4903
|
+
"polling_timeout"
|
|
4904
|
+
);
|
|
4905
|
+
return;
|
|
4906
|
+
}
|
|
4907
|
+
this.pollInFlight = true;
|
|
4908
|
+
try {
|
|
4909
|
+
const execution = await this.options.getExecution(this.options.executionId);
|
|
4910
|
+
this.pollConsecutiveFailures = 0;
|
|
4911
|
+
this.pollConsecutiveRateLimits = 0;
|
|
4912
|
+
this.pollConsecutiveClientErrors = 0;
|
|
4913
|
+
let sawNewInfo = false;
|
|
4914
|
+
for (const log of execution.logs ?? []) {
|
|
4915
|
+
if (!this.seenEventIds.has(log.uuid)) {
|
|
4916
|
+
this.seenEventIds.add(log.uuid);
|
|
4917
|
+
this.emit("log", log);
|
|
4918
|
+
sawNewInfo = true;
|
|
4656
4919
|
}
|
|
4657
|
-
} catch (error) {
|
|
4658
|
-
console.error("Polling error:", error);
|
|
4659
4920
|
}
|
|
4660
|
-
|
|
4921
|
+
if (execution.status !== this.lastPolledStatus) {
|
|
4922
|
+
this.lastPolledStatus = execution.status;
|
|
4923
|
+
sawNewInfo = true;
|
|
4924
|
+
}
|
|
4925
|
+
if (sawNewInfo) {
|
|
4926
|
+
this.pollLastProgressAt = Date.now();
|
|
4927
|
+
}
|
|
4928
|
+
if (execution.status === "completed") {
|
|
4929
|
+
this.stopPolling();
|
|
4930
|
+
this.handleComplete(execution);
|
|
4931
|
+
return;
|
|
4932
|
+
}
|
|
4933
|
+
if (execution.status === "failed" || execution.status === "cancelled") {
|
|
4934
|
+
this.stopPolling();
|
|
4935
|
+
this.handleError({
|
|
4936
|
+
error: execution.error ?? `Execution ${execution.status}`,
|
|
4937
|
+
errorCode: execution.status
|
|
4938
|
+
});
|
|
4939
|
+
return;
|
|
4940
|
+
}
|
|
4941
|
+
this.pollDelayMs = sawNewInfo ? this.pollBaseIntervalMs : nextIdleDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
4942
|
+
} catch (error) {
|
|
4943
|
+
if (!this.handlePollError(error)) {
|
|
4944
|
+
return;
|
|
4945
|
+
}
|
|
4946
|
+
} finally {
|
|
4947
|
+
this.pollInFlight = false;
|
|
4948
|
+
}
|
|
4949
|
+
this.schedulePoll(applyJitter(this.pollDelayMs, this.pollJitterRatio));
|
|
4950
|
+
}
|
|
4951
|
+
/**
|
|
4952
|
+
* Apply backpressure for a failed poll.
|
|
4953
|
+
*
|
|
4954
|
+
* @returns `true` to keep polling (with `pollDelayMs` updated), `false` when
|
|
4955
|
+
* the loop has been terminated.
|
|
4956
|
+
*/
|
|
4957
|
+
handlePollError(error) {
|
|
4958
|
+
const retryAfterMs = getRetryAfterMs(error);
|
|
4959
|
+
const rateLimited = isRateLimited(error) || retryAfterMs !== null;
|
|
4960
|
+
if (rateLimited) {
|
|
4961
|
+
this.pollConsecutiveRateLimits++;
|
|
4962
|
+
console.warn(
|
|
4963
|
+
"[SDK:StreamController] Poll rate-limited",
|
|
4964
|
+
retryAfterMs !== null ? `(server asked for ${Math.round(retryAfterMs / 1e3)}s)` : "(no reset named)"
|
|
4965
|
+
);
|
|
4966
|
+
if (retryAfterMs !== null && retryAfterMs > this.pollMaxRetryAfterMs) {
|
|
4967
|
+
this.failPolling(
|
|
4968
|
+
`Rate limited: the API asked for ${Math.round(retryAfterMs / 1e3)}s, longer than this stream will wait`,
|
|
4969
|
+
"rate_limited"
|
|
4970
|
+
);
|
|
4971
|
+
return false;
|
|
4972
|
+
}
|
|
4973
|
+
if (this.pollConsecutiveRateLimits >= this.pollMaxConsecutiveRateLimits) {
|
|
4974
|
+
this.failPolling(
|
|
4975
|
+
"Rate limited by the API; stopped the streaming fallback rather than keep spending the key quota",
|
|
4976
|
+
"rate_limited"
|
|
4977
|
+
);
|
|
4978
|
+
return false;
|
|
4979
|
+
}
|
|
4980
|
+
this.pollDelayMs = Math.max(retryAfterMs ?? UNNAMED_RATE_LIMIT_WAIT_MS, this.pollDelayMs);
|
|
4981
|
+
return true;
|
|
4982
|
+
}
|
|
4983
|
+
if (isTerminalClientError(error)) {
|
|
4984
|
+
this.pollConsecutiveClientErrors++;
|
|
4985
|
+
const status = getHttpStatus(error);
|
|
4986
|
+
console.warn(
|
|
4987
|
+
`[SDK:StreamController] Poll got HTTP ${status} (${this.pollConsecutiveClientErrors}/${this.pollMaxConsecutiveClientErrors})`
|
|
4988
|
+
);
|
|
4989
|
+
if (this.pollConsecutiveClientErrors >= this.pollMaxConsecutiveClientErrors) {
|
|
4990
|
+
this.failPolling(
|
|
4991
|
+
`Execution status endpoint returned HTTP ${status} ${this.pollConsecutiveClientErrors} times \u2014 cannot track this run`,
|
|
4992
|
+
"polling_unrecoverable"
|
|
4993
|
+
);
|
|
4994
|
+
return false;
|
|
4995
|
+
}
|
|
4996
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
4997
|
+
return true;
|
|
4998
|
+
}
|
|
4999
|
+
this.pollConsecutiveFailures++;
|
|
5000
|
+
console.warn(
|
|
5001
|
+
`[SDK:StreamController] Poll failed (${this.pollConsecutiveFailures}/${this.pollMaxConsecutiveFailures}):`,
|
|
5002
|
+
error
|
|
5003
|
+
);
|
|
5004
|
+
if (this.pollConsecutiveFailures >= this.pollMaxConsecutiveFailures) {
|
|
5005
|
+
this.failPolling(
|
|
5006
|
+
`Streaming fallback failed ${this.pollConsecutiveFailures} times in a row`,
|
|
5007
|
+
"polling_failed"
|
|
5008
|
+
);
|
|
5009
|
+
return false;
|
|
5010
|
+
}
|
|
5011
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
5012
|
+
return true;
|
|
5013
|
+
}
|
|
5014
|
+
/**
|
|
5015
|
+
* Terminate the fallback loop and surface the reason.
|
|
5016
|
+
*/
|
|
5017
|
+
failPolling(error, errorCode) {
|
|
5018
|
+
this.stopPolling();
|
|
5019
|
+
this.handleError({ error, errorCode });
|
|
4661
5020
|
}
|
|
4662
5021
|
/**
|
|
4663
|
-
* Stop polling
|
|
5022
|
+
* Stop polling, including the periodic SSE re-attempt that belongs to it.
|
|
4664
5023
|
*/
|
|
4665
5024
|
stopPolling() {
|
|
4666
|
-
if (this.
|
|
4667
|
-
|
|
4668
|
-
this.
|
|
5025
|
+
if (this.pollTimeoutId !== null) {
|
|
5026
|
+
clearTimeout(this.pollTimeoutId);
|
|
5027
|
+
this.pollTimeoutId = null;
|
|
5028
|
+
}
|
|
5029
|
+
if (this.sseRetryTimeoutId !== null) {
|
|
5030
|
+
clearTimeout(this.sseRetryTimeoutId);
|
|
5031
|
+
this.sseRetryTimeoutId = null;
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
/**
|
|
5035
|
+
* Queue the next attempt to get the real stream back.
|
|
5036
|
+
*/
|
|
5037
|
+
scheduleSSERetry() {
|
|
5038
|
+
if (this.sseRetryTimeoutId !== null || this.status !== "fallback_polling") {
|
|
5039
|
+
return;
|
|
4669
5040
|
}
|
|
5041
|
+
this.sseRetryTimeoutId = setTimeout(() => {
|
|
5042
|
+
this.sseRetryTimeoutId = null;
|
|
5043
|
+
this.attemptSSERecovery();
|
|
5044
|
+
}, this.sseRetryIntervalMs);
|
|
5045
|
+
}
|
|
5046
|
+
/**
|
|
5047
|
+
* Re-attempt SSE from inside the fallback, so a transient outage costs the
|
|
5048
|
+
* session a few polls rather than its stream for the lifetime of the tab.
|
|
5049
|
+
*
|
|
5050
|
+
* Success is NOT observed here: `SSEClient.connect()` resolves when the
|
|
5051
|
+
* stream ENDS, not when it opens. The open is observed by
|
|
5052
|
+
* `onConnectionChange(true)`, which flips status and stops this loop. The
|
|
5053
|
+
* promise below only tells us the attempt is over so the next one can be
|
|
5054
|
+
* queued.
|
|
5055
|
+
*/
|
|
5056
|
+
attemptSSERecovery() {
|
|
5057
|
+
if (this.status !== "fallback_polling") {
|
|
5058
|
+
return;
|
|
5059
|
+
}
|
|
5060
|
+
if (this.sseRetryInFlight) {
|
|
5061
|
+
this.scheduleSSERetry();
|
|
5062
|
+
return;
|
|
5063
|
+
}
|
|
5064
|
+
this.sseRetryInFlight = true;
|
|
5065
|
+
void this.connectSSE().catch(() => {
|
|
5066
|
+
}).finally(() => {
|
|
5067
|
+
this.sseRetryInFlight = false;
|
|
5068
|
+
this.scheduleSSERetry();
|
|
5069
|
+
});
|
|
4670
5070
|
}
|
|
4671
5071
|
/**
|
|
4672
5072
|
* Reset activity timeout
|
|
@@ -4798,12 +5198,23 @@ async function collectImagesFromStream(stream, options = {}) {
|
|
|
4798
5198
|
});
|
|
4799
5199
|
}
|
|
4800
5200
|
|
|
5201
|
+
// src/utils/api-paths.ts
|
|
5202
|
+
var API_V1 = "/api/v1";
|
|
5203
|
+
var EXECUTION_PATH = `${API_V1}/executions`;
|
|
5204
|
+
function executionPath(executionId) {
|
|
5205
|
+
return `${EXECUTION_PATH}/${executionId}`;
|
|
5206
|
+
}
|
|
5207
|
+
function executionCancelPath(executionId) {
|
|
5208
|
+
return `${EXECUTION_PATH}/${executionId}/cancel`;
|
|
5209
|
+
}
|
|
5210
|
+
|
|
4801
5211
|
// src/resources/agents.ts
|
|
4802
5212
|
var AgentsAPI = class extends BaseResourceAPI {
|
|
4803
5213
|
constructor(http, getStreamConfig) {
|
|
4804
5214
|
super(http, "/api/v1/agents");
|
|
4805
5215
|
this.getStreamConfig = getStreamConfig;
|
|
4806
5216
|
}
|
|
5217
|
+
getStreamConfig;
|
|
4807
5218
|
// ===== LIST Operations =====
|
|
4808
5219
|
/**
|
|
4809
5220
|
* List all agents in the organization
|
|
@@ -5060,7 +5471,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
5060
5471
|
* ```
|
|
5061
5472
|
*/
|
|
5062
5473
|
async getExecution(executionId) {
|
|
5063
|
-
return this.http.get(
|
|
5474
|
+
return this.http.get(executionPath(executionId));
|
|
5064
5475
|
}
|
|
5065
5476
|
// ===== EDIT Operations =====
|
|
5066
5477
|
/**
|
|
@@ -5115,7 +5526,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
5115
5526
|
*/
|
|
5116
5527
|
async cancelExecution(executionId) {
|
|
5117
5528
|
return this.http.post(
|
|
5118
|
-
|
|
5529
|
+
executionCancelPath(executionId),
|
|
5119
5530
|
void 0,
|
|
5120
5531
|
{ skipRetry: true }
|
|
5121
5532
|
);
|
|
@@ -5616,6 +6027,7 @@ var ArtifactsAPI = class extends BaseResourceAPI {
|
|
|
5616
6027
|
}
|
|
5617
6028
|
return this.http.request({
|
|
5618
6029
|
method: "POST",
|
|
6030
|
+
// KNOWN BROKEN — same missing `/api/v1` prefix as listArtifacts above.
|
|
5619
6031
|
path: `/executions/${executionId}/artifacts`,
|
|
5620
6032
|
body: formData,
|
|
5621
6033
|
headers: {
|
|
@@ -5709,6 +6121,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
5709
6121
|
super(http, "/api/v1/sessions");
|
|
5710
6122
|
this.getStreamConfig = getStreamConfig;
|
|
5711
6123
|
}
|
|
6124
|
+
getStreamConfig;
|
|
5712
6125
|
// ===== LIST Operations =====
|
|
5713
6126
|
/**
|
|
5714
6127
|
* List all sessions in the organization
|
|
@@ -5888,7 +6301,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
5888
6301
|
* Get execution result
|
|
5889
6302
|
*/
|
|
5890
6303
|
async getExecution(executionId) {
|
|
5891
|
-
return this.http.get(
|
|
6304
|
+
return this.http.get(executionPath(executionId));
|
|
5892
6305
|
}
|
|
5893
6306
|
// ===== EDIT Operations =====
|
|
5894
6307
|
/**
|
|
@@ -6028,6 +6441,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
6028
6441
|
super(http, "/api/v1/kanban");
|
|
6029
6442
|
this.getStreamConfig = getStreamConfig;
|
|
6030
6443
|
}
|
|
6444
|
+
getStreamConfig;
|
|
6031
6445
|
// ===== BOARD =====
|
|
6032
6446
|
/**
|
|
6033
6447
|
* Get the current org's board (lanes in declared order). Auto-provisions
|
|
@@ -6162,7 +6576,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
6162
6576
|
* @returns Execution details.
|
|
6163
6577
|
*/
|
|
6164
6578
|
async getCardRun(executionId) {
|
|
6165
|
-
return this.http.get(
|
|
6579
|
+
return this.http.get(executionPath(executionId));
|
|
6166
6580
|
}
|
|
6167
6581
|
/**
|
|
6168
6582
|
* Create a stream controller for a card's run, keyed on the card's
|
|
@@ -6229,6 +6643,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
6229
6643
|
super(http, "/api/v1/remote-agents");
|
|
6230
6644
|
this.getStreamConfig = getStreamConfig;
|
|
6231
6645
|
}
|
|
6646
|
+
getStreamConfig;
|
|
6232
6647
|
/**
|
|
6233
6648
|
* List all remote agents in the organization
|
|
6234
6649
|
*
|
|
@@ -6362,7 +6777,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
6362
6777
|
apiKey: config.apiKey,
|
|
6363
6778
|
tokenProvider: config.tokenProvider,
|
|
6364
6779
|
orgId: config.orgId,
|
|
6365
|
-
getExecution: (id) => this.http.get(
|
|
6780
|
+
getExecution: (id) => this.http.get(executionPath(id))
|
|
6366
6781
|
});
|
|
6367
6782
|
return controller;
|
|
6368
6783
|
}
|