@datalayer/agent-runtimes 1.5.50 → 1.5.52
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.
|
@@ -36,6 +36,15 @@ export interface AgentClient<P> {
|
|
|
36
36
|
startTurn(request: HostTurnRequest<P>): Promise<TurnStream>;
|
|
37
37
|
stopTurn(sessionId: string, request: HostTurnRequest<P>): Promise<void>;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Maps a session to its host place again, once the place changed after the
|
|
41
|
+
* turn started (plans/DISCORD.md §3.2: Discord's answer thread exists only
|
|
42
|
+
* once the answer is shown, so the target is PUT again with it).
|
|
43
|
+
*/
|
|
44
|
+
export interface SessionBinder<P> {
|
|
45
|
+
/** Whether the session was mapped: `false` where nothing binds (development's credentials). */
|
|
46
|
+
bindSession(sessionId: string, request: HostTurnRequest<P>): Promise<boolean>;
|
|
47
|
+
}
|
|
39
48
|
/** Where a host asks from (`exchange.optional_request.place`): a channel, or a direct conversation. */
|
|
40
49
|
export type Place = Readonly<{
|
|
41
50
|
kind: 'channel' | 'direct';
|
|
@@ -154,13 +163,7 @@ export declare class HostStarters<K, P> implements StarterSource<K> {
|
|
|
154
163
|
}
|
|
155
164
|
/** Each AG-UI event of a run's `text/event-stream` body, as it arrives (`AgUiFrames`). */
|
|
156
165
|
export declare function agUiEventsOf(body: ReadableStream<Uint8Array>): AsyncIterable<AgUiEvent>;
|
|
157
|
-
|
|
158
|
-
* The host's Datalayer client: a turn is started or sent on the deployment's
|
|
159
|
-
* session API (idempotent by the host's event id, SLACK §4.3, 2), its AG-UI
|
|
160
|
-
* stream reduced to semantic events, and its session mapped to the host's
|
|
161
|
-
* thread so that approvals and completions are delivered (§4.3, 3).
|
|
162
|
-
*/
|
|
163
|
-
export declare class HttpAgentClient<P> implements AgentClient<P> {
|
|
166
|
+
export declare class HttpAgentClient<P> implements AgentClient<P>, SessionBinder<P> {
|
|
164
167
|
#private;
|
|
165
168
|
readonly apiUrl: string;
|
|
166
169
|
readonly credentials: HostCredentials<P>;
|
|
@@ -169,6 +172,11 @@ export declare class HttpAgentClient<P> implements AgentClient<P> {
|
|
|
169
172
|
readonly hostKeyFor?: HostKeyLookup<P> | undefined;
|
|
170
173
|
constructor(apiUrl: string, credentials: HostCredentials<P>, shape: HostShape<P>, fetchImplementation?: typeof fetch, hostKeyFor?: HostKeyLookup<P> | undefined);
|
|
171
174
|
startTurn(request: HostTurnRequest<P>): Promise<TurnStream>;
|
|
175
|
+
/**
|
|
176
|
+
* The session mapped again with the shape's target for `request`, as the
|
|
177
|
+
* speaker (a fresh exchange): the binding is replaced, never doubled.
|
|
178
|
+
*/
|
|
179
|
+
bindSession(sessionId: string, request: HostTurnRequest<P>): Promise<boolean>;
|
|
172
180
|
stopTurn(sessionId: string, request: HostTurnRequest<P>): Promise<void>;
|
|
173
181
|
}
|
|
174
182
|
/**
|
|
@@ -118,12 +118,16 @@ export class DevelopmentCredentials {
|
|
|
118
118
|
async forTurn(request) {
|
|
119
119
|
const response = await this.fetchImplementation(`${this.apiUrl}/api/ai-agents/v1/apps/deployments/${encodeURIComponent(request.applicationId)}`, { headers: { authorization: `Bearer ${this.token}` } });
|
|
120
120
|
if (!response.ok) {
|
|
121
|
-
throw new
|
|
121
|
+
throw new HostRefusedError(response.status, 'Datalayer did not answer the deployment.', 'The deployment');
|
|
122
122
|
}
|
|
123
123
|
const body = (await response.json());
|
|
124
124
|
const kept = body.deployment?.kept;
|
|
125
125
|
if (!kept?.url || !kept.agent_id) {
|
|
126
|
-
|
|
126
|
+
// A refusal the host says to the person (409), with the deployment's
|
|
127
|
+
// own reason when it gives one (a daily credit limit spent, paused).
|
|
128
|
+
throw new HostRefusedError(409, kept?.why
|
|
129
|
+
? `The deployment is not answering now: ${kept.why}`
|
|
130
|
+
: 'The deployment is not kept always on: turn it on under Ship.', 'The deployment');
|
|
127
131
|
}
|
|
128
132
|
return {
|
|
129
133
|
accessToken: this.token,
|
|
@@ -203,6 +207,19 @@ export async function* agUiEventsOf(body) {
|
|
|
203
207
|
* stream reduced to semantic events, and its session mapped to the host's
|
|
204
208
|
* thread so that approvals and completions are delivered (§4.3, 3).
|
|
205
209
|
*/
|
|
210
|
+
/** A refusal's `detail` when the answer is FastAPI's JSON, its status alone otherwise. */
|
|
211
|
+
async function refusalDetailOf(response) {
|
|
212
|
+
const text = await response.text().catch(() => '');
|
|
213
|
+
try {
|
|
214
|
+
const detail = JSON.parse(text).detail;
|
|
215
|
+
if (typeof detail === 'string' && detail)
|
|
216
|
+
return detail;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Not JSON: the status says it.
|
|
220
|
+
}
|
|
221
|
+
return `Datalayer refused the turn (${response.status}).`;
|
|
222
|
+
}
|
|
206
223
|
export class HttpAgentClient {
|
|
207
224
|
apiUrl;
|
|
208
225
|
credentials;
|
|
@@ -251,7 +268,10 @@ export class HttpAgentClient {
|
|
|
251
268
|
method: 'POST',
|
|
252
269
|
});
|
|
253
270
|
if (!response.ok || !response.body) {
|
|
254
|
-
|
|
271
|
+
// The runtime's own reason, said to the person by the host: an
|
|
272
|
+
// application that signs its users and got no user token, a spent
|
|
273
|
+
// limit, a session of someone else.
|
|
274
|
+
throw new HostRefusedError(response.status, await refusalDetailOf(response), 'The turn');
|
|
255
275
|
}
|
|
256
276
|
const hostKey = host.bindable && this.hostKeyFor
|
|
257
277
|
? await this.hostKeyFor(request)
|
|
@@ -266,6 +286,20 @@ export class HttpAgentClient {
|
|
|
266
286
|
updates: this.#updates(response.body, host, sessionId, run),
|
|
267
287
|
};
|
|
268
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* The session mapped again with the shape's target for `request`, as the
|
|
291
|
+
* speaker (a fresh exchange): the binding is replaced, never doubled.
|
|
292
|
+
*/
|
|
293
|
+
async bindSession(sessionId, request) {
|
|
294
|
+
const host = await this.credentials.forTurn(request);
|
|
295
|
+
const hostKey = host.bindable && this.hostKeyFor
|
|
296
|
+
? await this.hostKeyFor(request)
|
|
297
|
+
: undefined;
|
|
298
|
+
if (!hostKey)
|
|
299
|
+
return false;
|
|
300
|
+
await this.#bind(request, host, sessionId, hostKey);
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
269
303
|
async stopTurn(sessionId, request) {
|
|
270
304
|
const host = await this.credentials.forTurn(request);
|
|
271
305
|
assertTrustedStreamUrl(this.apiUrl, host.sessionApiUrl);
|
|
@@ -22,7 +22,7 @@ export * from '../semantic';
|
|
|
22
22
|
export { askQuestionOf, semanticOf, turnOf, type TurnInfo, } from '../semanticOfAgUi';
|
|
23
23
|
export * from './contracts';
|
|
24
24
|
export { DatalayerHostClient, HostRefusedError, type BoundHost, type HostSelf, } from './client';
|
|
25
|
-
export { agUiEventsOf, assertTrustedStreamUrl, DevelopmentCredentials, HostKeyCredentials, hostSessionOf, HostStarters, HttpAgentClient, InstallationCredentials, lastAnswer, type AgentClient, type HostCredentials, type HostKeyLookup, type HostSession, type HostShape, type HostTurnRequest, type Place, type Starter, type StarterSource, type TurnProvenance, type TurnStream, } from './agentClient';
|
|
25
|
+
export { agUiEventsOf, assertTrustedStreamUrl, DevelopmentCredentials, HostKeyCredentials, hostSessionOf, HostStarters, HttpAgentClient, InstallationCredentials, lastAnswer, type AgentClient, type HostCredentials, type HostKeyLookup, type HostSession, type HostShape, type HostTurnRequest, type Place, type SessionBinder, type Starter, type StarterSource, type TurnProvenance, type TurnStream, } from './agentClient';
|
|
26
26
|
export { hostSessionUid } from './sessionUid';
|
|
27
27
|
export { DATALAYER_ONLY, MAIL_WORDS, RENDERERS, renderApproval, type RenderedAction, type RenderedField, } from './approvalRenderers';
|
|
28
28
|
export { DELIVERY_PATH, DELIVERY_ROUTE, DeliveryPoller, deliveryRoute, receiveDelivery, type BoundHostKey, type DeliveryClaimStore, type DeliveryJob, type DeliverySink, } from './deliveries';
|
|
@@ -40,6 +40,13 @@ export interface WorkQueue<T> {
|
|
|
40
40
|
/** In-process development queue. A durable adapter is required in production. */
|
|
41
41
|
export declare class MemoryWorkQueue<T> implements WorkQueue<T> {
|
|
42
42
|
#private;
|
|
43
|
+
readonly onFailure: (error: unknown, item: T) => void;
|
|
44
|
+
/**
|
|
45
|
+
* A job that throws is reported here and dropped, and the next one is
|
|
46
|
+
* worked: one failure never stops the queue, nor the process with an
|
|
47
|
+
* unhandled rejection. The durable queue retries and dead-letters instead.
|
|
48
|
+
*/
|
|
49
|
+
constructor(onFailure?: (error: unknown, item: T) => void);
|
|
43
50
|
enqueue(item: T, idempotencyKey?: string): Promise<boolean>;
|
|
44
51
|
ready(): Promise<boolean>;
|
|
45
52
|
start(handler: (item: T) => Promise<void>): void;
|
|
@@ -11,10 +11,21 @@ export function tablePrefixOf(prefix) {
|
|
|
11
11
|
}
|
|
12
12
|
/** In-process development queue. A durable adapter is required in production. */
|
|
13
13
|
export class MemoryWorkQueue {
|
|
14
|
+
onFailure;
|
|
14
15
|
#items = [];
|
|
15
16
|
#keys = new Set();
|
|
16
17
|
#handler;
|
|
17
18
|
#running = false;
|
|
19
|
+
/**
|
|
20
|
+
* A job that throws is reported here and dropped, and the next one is
|
|
21
|
+
* worked: one failure never stops the queue, nor the process with an
|
|
22
|
+
* unhandled rejection. The durable queue retries and dead-letters instead.
|
|
23
|
+
*/
|
|
24
|
+
constructor(onFailure = error => {
|
|
25
|
+
console.error(`A job failed and was dropped: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
+
}) {
|
|
27
|
+
this.onFailure = onFailure;
|
|
28
|
+
}
|
|
18
29
|
async enqueue(item, idempotencyKey) {
|
|
19
30
|
if (idempotencyKey) {
|
|
20
31
|
if (this.#keys.has(idempotencyKey)) {
|
|
@@ -52,7 +63,12 @@ export class MemoryWorkQueue {
|
|
|
52
63
|
}
|
|
53
64
|
const item = this.#items.shift();
|
|
54
65
|
if (item !== undefined) {
|
|
55
|
-
|
|
66
|
+
try {
|
|
67
|
+
await handler(item);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
this.onFailure(error, item);
|
|
71
|
+
}
|
|
56
72
|
}
|
|
57
73
|
}
|
|
58
74
|
}
|