@datalayer/agent-runtimes 1.5.51 → 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.
@@ -163,12 +163,6 @@ export declare class HostStarters<K, P> implements StarterSource<K> {
163
163
  }
164
164
  /** Each AG-UI event of a run's `text/event-stream` body, as it arrives (`AgUiFrames`). */
165
165
  export declare function agUiEventsOf(body: ReadableStream<Uint8Array>): AsyncIterable<AgUiEvent>;
166
- /**
167
- * The host's Datalayer client: a turn is started or sent on the deployment's
168
- * session API (idempotent by the host's event id, SLACK §4.3, 2), its AG-UI
169
- * stream reduced to semantic events, and its session mapped to the host's
170
- * thread so that approvals and completions are delivered (§4.3, 3).
171
- */
172
166
  export declare class HttpAgentClient<P> implements AgentClient<P>, SessionBinder<P> {
173
167
  #private;
174
168
  readonly apiUrl: string;
@@ -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 Error(`Datalayer did not answer the deployment (${response.status}).`);
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
- throw new Error('The deployment is not kept always on: turn it on under Ship.');
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
- throw new Error(`Datalayer refused the turn (${response.status}).`);
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)
@@ -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
- await handler(item);
66
+ try {
67
+ await handler(item);
68
+ }
69
+ catch (error) {
70
+ this.onFailure(error, item);
71
+ }
56
72
  }
57
73
  }
58
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.5.51",
3
+ "version": "1.5.52",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",