@gakim-digital/dexter-bridge 0.5.7 → 0.5.8
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/package.json +1 -1
- package/src/agent.js +74 -38
- package/src/api.js +10 -1
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -108,45 +108,81 @@ function nowIso() {
|
|
|
108
108
|
return new Date().toISOString();
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
const TERMINAL_EVENT_TYPES = new Set(['done', 'error']);
|
|
112
|
+
const TERMINAL_EVENT_MAX_ATTEMPTS = 5;
|
|
113
|
+
|
|
114
|
+
function waitForRetry(delayMs) {
|
|
115
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function terminalEventRetryDelay(error, attempt) {
|
|
119
|
+
const status = Number(error?.status);
|
|
120
|
+
const retryable =
|
|
121
|
+
!Number.isFinite(status)
|
|
122
|
+
|| status === 408
|
|
123
|
+
|| status === 429
|
|
124
|
+
|| status >= 500;
|
|
125
|
+
if (!retryable) return null;
|
|
126
|
+
if (Number.isFinite(error?.retryAfterMs) && error.retryAfterMs > 0) {
|
|
127
|
+
return Math.min(65_000, Math.max(250, error.retryAfterMs));
|
|
128
|
+
}
|
|
129
|
+
return Math.min(8_000, 500 * (2 ** (attempt - 1)));
|
|
130
|
+
}
|
|
131
|
+
|
|
111
132
|
function createEventPoster({ apiBaseUrl, deviceToken, run, fetchImpl, trace }) {
|
|
112
133
|
return async function send(type, payload = {}) {
|
|
113
134
|
const eventId = `${type}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
114
135
|
const started = Date.now();
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const response = await postRunEvent(apiBaseUrl, {
|
|
122
|
-
deviceToken,
|
|
123
|
-
runId: run.runId,
|
|
124
|
-
fetchImpl,
|
|
125
|
-
event: {
|
|
126
|
-
type,
|
|
127
|
-
eventId,
|
|
128
|
-
payload,
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
trace?.info('event_post_done', {
|
|
132
|
-
type,
|
|
133
|
-
eventId,
|
|
134
|
-
durationMs: Date.now() - started,
|
|
135
|
-
accepted: response?.accepted,
|
|
136
|
-
status: response?.status,
|
|
137
|
-
hasToolResult: Boolean(response?.toolResult),
|
|
138
|
-
toolResult: summarizeToolResult(response?.toolResult),
|
|
139
|
-
});
|
|
140
|
-
return response;
|
|
141
|
-
} catch (error) {
|
|
142
|
-
trace?.error('event_post_failed', {
|
|
136
|
+
const maxAttempts = TERMINAL_EVENT_TYPES.has(type)
|
|
137
|
+
? TERMINAL_EVENT_MAX_ATTEMPTS
|
|
138
|
+
: 1;
|
|
139
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
140
|
+
trace?.info('event_post_start', {
|
|
143
141
|
type,
|
|
144
142
|
eventId,
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
attempt,
|
|
144
|
+
payload: summarizePayload(payload),
|
|
147
145
|
});
|
|
148
|
-
|
|
146
|
+
try {
|
|
147
|
+
const response = await postRunEvent(apiBaseUrl, {
|
|
148
|
+
deviceToken,
|
|
149
|
+
runId: run.runId,
|
|
150
|
+
fetchImpl,
|
|
151
|
+
event: {
|
|
152
|
+
type,
|
|
153
|
+
eventId,
|
|
154
|
+
payload,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
trace?.info('event_post_done', {
|
|
158
|
+
type,
|
|
159
|
+
eventId,
|
|
160
|
+
attempt,
|
|
161
|
+
durationMs: Date.now() - started,
|
|
162
|
+
accepted: response?.accepted,
|
|
163
|
+
status: response?.status,
|
|
164
|
+
hasToolResult: Boolean(response?.toolResult),
|
|
165
|
+
toolResult: summarizeToolResult(response?.toolResult),
|
|
166
|
+
});
|
|
167
|
+
return response;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const retryDelayMs =
|
|
170
|
+
attempt < maxAttempts
|
|
171
|
+
? terminalEventRetryDelay(error, attempt)
|
|
172
|
+
: null;
|
|
173
|
+
trace?.error('event_post_failed', {
|
|
174
|
+
type,
|
|
175
|
+
eventId,
|
|
176
|
+
attempt,
|
|
177
|
+
durationMs: Date.now() - started,
|
|
178
|
+
retryDelayMs,
|
|
179
|
+
error: errorMeta(error),
|
|
180
|
+
});
|
|
181
|
+
if (retryDelayMs === null) throw error;
|
|
182
|
+
await waitForRetry(retryDelayMs);
|
|
183
|
+
}
|
|
149
184
|
}
|
|
185
|
+
throw new Error(`Unable to deliver ${type} event.`);
|
|
150
186
|
};
|
|
151
187
|
}
|
|
152
188
|
|
|
@@ -887,7 +923,7 @@ class CompanionRunCancelledError extends Error {
|
|
|
887
923
|
async function callProviderAdapter(adapter, input, {
|
|
888
924
|
send,
|
|
889
925
|
trace,
|
|
890
|
-
controlPollMs =
|
|
926
|
+
controlPollMs = 5_000,
|
|
891
927
|
} = {}) {
|
|
892
928
|
if (!adapter || typeof adapter.runModelTurn !== 'function') {
|
|
893
929
|
throw new Error('The selected provider adapter cannot execute model turns.');
|
|
@@ -1209,9 +1245,9 @@ async function executeModelTurnRun(run, send, agent, options = {}) {
|
|
|
1209
1245
|
trace: options.trace,
|
|
1210
1246
|
controlPollMs: boundedDurationMs(
|
|
1211
1247
|
options.controlPollMs ?? options.env?.DEXTER_BRIDGE_CONTROL_POLL_MS,
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1248
|
+
5_000,
|
|
1249
|
+
1_000,
|
|
1250
|
+
30_000,
|
|
1215
1251
|
),
|
|
1216
1252
|
};
|
|
1217
1253
|
let result;
|
|
@@ -1253,9 +1289,9 @@ async function executeModelTurnRun(run, send, agent, options = {}) {
|
|
|
1253
1289
|
const monitorAbort = new AbortController();
|
|
1254
1290
|
const controlPollMs = boundedDurationMs(
|
|
1255
1291
|
options.controlPollMs ?? options.env?.DEXTER_BRIDGE_CONTROL_POLL_MS,
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1292
|
+
5_000,
|
|
1293
|
+
1_000,
|
|
1294
|
+
30_000,
|
|
1259
1295
|
);
|
|
1260
1296
|
let cliFinished = false;
|
|
1261
1297
|
let cliCancelled = false;
|
package/src/api.js
CHANGED
|
@@ -7,11 +7,12 @@ import {
|
|
|
7
7
|
} from './config.js';
|
|
8
8
|
|
|
9
9
|
export class DexterBridgeApiError extends Error {
|
|
10
|
-
constructor(message, status, body) {
|
|
10
|
+
constructor(message, status, body, retryAfterMs) {
|
|
11
11
|
super(message);
|
|
12
12
|
this.name = 'DexterBridgeApiError';
|
|
13
13
|
this.status = status;
|
|
14
14
|
this.body = body;
|
|
15
|
+
this.retryAfterMs = retryAfterMs;
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -50,10 +51,18 @@ export async function requestJson(apiBaseUrl, path, {
|
|
|
50
51
|
});
|
|
51
52
|
const parsed = await response.json().catch(() => null);
|
|
52
53
|
if (!response.ok || parsed?.success === false) {
|
|
54
|
+
const retryAfterHeader = Number(response.headers?.get?.('retry-after'));
|
|
55
|
+
const retryAfterSeconds = Number(parsed?.retryAfterSeconds);
|
|
56
|
+
const retryAfterMs = Number.isFinite(retryAfterHeader) && retryAfterHeader > 0
|
|
57
|
+
? retryAfterHeader * 1000
|
|
58
|
+
: Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
|
|
59
|
+
? retryAfterSeconds * 1000
|
|
60
|
+
: undefined;
|
|
53
61
|
throw new DexterBridgeApiError(
|
|
54
62
|
parsed?.error || parsed?.message || `Dexter API request failed with status ${response.status}`,
|
|
55
63
|
response.status,
|
|
56
64
|
parsed,
|
|
65
|
+
retryAfterMs,
|
|
57
66
|
);
|
|
58
67
|
}
|
|
59
68
|
return parsed;
|