@datalayer/agent-runtimes 1.5.48 → 1.5.49

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.
@@ -41,16 +41,29 @@ export type Place = Readonly<{
41
41
  kind: 'channel' | 'direct';
42
42
  id: string;
43
43
  }>;
44
+ /**
45
+ * Where a turn comes from, as the session API takes it on the turn itself
46
+ * (`provenance`, plans/DISCORD.md §4.3, §4.4; at most 1 KiB): the host's
47
+ * kind, the place, the thread and the message.
48
+ */
49
+ export type TurnProvenance = Readonly<{
50
+ host_kind: string;
51
+ place: Place;
52
+ thread: string;
53
+ message_id: string;
54
+ }>;
44
55
  /**
45
56
  * What is the host's own in a turn: the session uid its thread derives
46
57
  * (`hostSessionUid`), the place Datalayer checks the channel policy against,
47
- * the delivery target handed back with every delivery, and what is said when
48
- * its installation holds no host key.
58
+ * the delivery target handed back with every delivery, the provenance sent
59
+ * with the turn, and what is said when its installation holds no host key.
49
60
  */
50
61
  export type HostShape<P> = Readonly<{
51
62
  sessionUidOf(request: HostTurnRequest<P>): string;
52
63
  placeOf(request: HostTurnRequest<P>): Place;
53
64
  targetOf(request: HostTurnRequest<P>): Record<string, unknown>;
65
+ /** The turn's provenance; a host that sends none leaves it out. */
66
+ provenanceOf?(request: HostTurnRequest<P>): TurnProvenance;
54
67
  notBound: string;
55
68
  }>;
56
69
  /**
@@ -220,6 +220,7 @@ export class HttpAgentClient {
220
220
  const host = await this.credentials.forTurn(request);
221
221
  assertTrustedStreamUrl(this.apiUrl, host.sessionApiUrl);
222
222
  const sessionId = request.sessionId ?? this.shape.sessionUidOf(request);
223
+ const provenance = this.shape.provenanceOf?.(request);
223
224
  const headers = {
224
225
  accept: 'text/event-stream',
225
226
  authorization: `Bearer ${host.accessToken}`,
@@ -231,6 +232,7 @@ export class HttpAgentClient {
231
232
  body: JSON.stringify({
232
233
  external_event_id: request.externalEventId,
233
234
  text: request.prompt,
235
+ ...(provenance ? { provenance } : {}),
234
236
  }),
235
237
  headers,
236
238
  method: 'POST',
@@ -243,6 +245,7 @@ export class HttpAgentClient {
243
245
  opener: request.prompt,
244
246
  session: sessionId,
245
247
  ...(host.userToken ? { user_token: host.userToken } : {}),
248
+ ...(provenance ? { provenance } : {}),
246
249
  }),
247
250
  headers,
248
251
  method: 'POST',
@@ -53,6 +53,22 @@ export declare class DatalayerHostClient {
53
53
  * channel policy and whether it is paused.
54
54
  */
55
55
  self(hostKey: string): Promise<HostSelf>;
56
+ /**
57
+ * The host's own administration (plans/DISCORD.md DC-106,
58
+ * `/datalayer-admin`): pause, resume or one channel's rule, for the host
59
+ * user linked to the installation's owner; Datalayer refuses anyone else
60
+ * (403), whatever their role in the host.
61
+ */
62
+ admin(hostKey: string, externalUserId: string, change: Readonly<{
63
+ paused?: boolean;
64
+ channel?: Readonly<{
65
+ id: string;
66
+ state: 'allow' | 'deny';
67
+ deployment?: string;
68
+ }>;
69
+ }>): Promise<{
70
+ paused: boolean;
71
+ }>;
56
72
  /** The host unlinks one of its users: how many links ended. */
57
73
  unlink(hostKey: string, externalUserId: string): Promise<number>;
58
74
  /** A one-use code a host user redeems, signed in to Datalayer, to link their account. */
@@ -102,6 +102,22 @@ export class DatalayerHostClient {
102
102
  paused: host.paused ?? false,
103
103
  };
104
104
  }
105
+ /**
106
+ * The host's own administration (plans/DISCORD.md DC-106,
107
+ * `/datalayer-admin`): pause, resume or one channel's rule, for the host
108
+ * user linked to the installation's owner; Datalayer refuses anyone else
109
+ * (403), whatever their role in the host.
110
+ */
111
+ async admin(hostKey, externalUserId, change) {
112
+ const answered = await this.#call('Datalayer did not take the change', '/apps/hosts/self/admin', {
113
+ body: { external_user_id: externalUserId, ...change },
114
+ hostKey,
115
+ method: 'POST',
116
+ }, z.object({
117
+ host: z.object({ paused: z.boolean().optional() }).loose(),
118
+ }));
119
+ return { paused: answered.host.paused ?? false };
120
+ }
105
121
  /** The host unlinks one of its users: how many links ended. */
106
122
  async unlink(hostKey, externalUserId) {
107
123
  const answered = await this.#call('Datalayer did not unlink the account', `/apps/hosts/self/links/${encodeURIComponent(externalUserId)}`, { hostKey, method: 'DELETE' }, z.object({ revoked: z.number().int() }));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "about": "The generic host contracts of plans/SLACK.md §4.3, as ai-agents and the runtime's session API answer them. Shared verbatim by ai-agents (tests/fixtures/hosts) and the Slack adapter (tech/datalayer/slack, test/contracts): each side's tests check its half against this file.",
3
+ "about": "The generic host contracts of plans/SLACK.md §4.3 and plans/DISCORD.md §4.3, as ai-agents and the runtime's session API answer them. Shared verbatim by ai-agents (tests/fixtures/hosts) and agent-runtimes (src/portable/hosts, read by the Slack and Discord adapters from lib/portable/hosts): each side's tests check its half against this file.",
4
4
  "headers": {
5
5
  "host_key": "X-Datalayer-Host-Key",
6
6
  "signature": "X-Datalayer-Signature",
@@ -29,7 +29,8 @@
29
29
  "preview_link": "GET /api/ai-agents/v1/apps/hosts/links/preview?code={code}",
30
30
  "change_host": "PATCH /api/ai-agents/v1/apps/hosts/{uid}",
31
31
  "handoff": "POST /api/ai-agents/v1/apps/hosts/handoffs",
32
- "redeem_handoff": "POST /api/ai-agents/v1/apps/hosts/handoffs/redeem"
32
+ "redeem_handoff": "POST /api/ai-agents/v1/apps/hosts/handoffs/redeem",
33
+ "admin": "POST /api/ai-agents/v1/apps/hosts/self/admin"
33
34
  },
34
35
  "bind": {
35
36
  "request": {
@@ -147,7 +148,19 @@
147
148
  "state"
148
149
  ]
149
150
  },
150
- "replay": "The same Idempotency-Key (or, without one, the same external_event_id) and the same request answer the original session and turn with replayed=true, and run nothing; the same key with another request is 409."
151
+ "replay": "The same Idempotency-Key (or, without one, the same external_event_id) and the same request answer the original session and turn with replayed=true, and run nothing; the same key with another request is 409.",
152
+ "optional_request": {
153
+ "provenance": {
154
+ "host_kind": "discord",
155
+ "place": {
156
+ "kind": "channel",
157
+ "id": "345678901234567890"
158
+ },
159
+ "thread": "456789012345678901",
160
+ "message_id": "456789012345678901"
161
+ }
162
+ },
163
+ "provenance_limit_bytes": 1024
151
164
  },
152
165
  "webhook": {
153
166
  "request": {
@@ -380,5 +393,75 @@
380
393
  "expired": 410,
381
394
  "spent_or_unknown": 404
382
395
  }
396
+ },
397
+ "admin": {
398
+ "request": {
399
+ "external_user_id": "567890123456789012",
400
+ "paused": true
401
+ },
402
+ "channel_request": {
403
+ "external_user_id": "567890123456789012",
404
+ "channel": {
405
+ "id": "345678901234567890",
406
+ "state": "deny"
407
+ }
408
+ },
409
+ "response_keys": [
410
+ "success",
411
+ "host"
412
+ ],
413
+ "refusals": {
414
+ "not_the_owner": 403,
415
+ "nothing_to_change": 422
416
+ }
417
+ },
418
+ "discord": {
419
+ "about": "Discord's forms of the generic contracts (plans/DISCORD.md §4.3): the installation is <application_id>:<guild_id>, a member is their snowflake, a thread is sent as its parent channel, the session uid is hostSessionUid(\"discord\", [application, guild, channel, thread]).",
420
+ "application_id": "123456789012345678",
421
+ "guild_id": "234567890123456789",
422
+ "bind_request": {
423
+ "code": "dlhc_…",
424
+ "external_id": "123456789012345678:234567890123456789",
425
+ "name": "Acme"
426
+ },
427
+ "external_user_id": "567890123456789012",
428
+ "thread": {
429
+ "channel": {
430
+ "id": "456789012345678901",
431
+ "type": 11,
432
+ "parent_id": "345678901234567890"
433
+ },
434
+ "place": {
435
+ "kind": "channel",
436
+ "id": "345678901234567890"
437
+ }
438
+ },
439
+ "direct": {
440
+ "channel": {
441
+ "id": "678901234567890123",
442
+ "type": 1
443
+ },
444
+ "place": {
445
+ "kind": "direct",
446
+ "id": "678901234567890123"
447
+ }
448
+ },
449
+ "session": {
450
+ "parts": [
451
+ "123456789012345678",
452
+ "234567890123456789",
453
+ "345678901234567890",
454
+ "456789012345678901"
455
+ ],
456
+ "uid": "discord-15beb80510370bbe027882df9d4d4666925017df"
457
+ },
458
+ "target": {
459
+ "applicationId": "123456789012345678",
460
+ "channelId": "345678901234567890",
461
+ "guildId": "234567890123456789",
462
+ "messageId": "456789012345678901",
463
+ "threadId": "456789012345678901",
464
+ "userId": "567890123456789012"
465
+ }
383
466
  }
384
467
  }
@@ -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 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 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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.5.48",
3
+ "version": "1.5.49",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",