@botiverse/raft-sdk 1.0.0-alpha.4 → 1.0.0-alpha.6

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/README.md CHANGED
@@ -147,10 +147,13 @@ if (!signal.ok) return new Response(signal.message, { status: 401 });
147
147
  - `raft.messages.search / resolve / react / unreact` — find a specific
148
148
  message (previews neutralise `@handles` and `#channels`), resolve one id to
149
149
  its canonical form and reply target, add or remove a reaction.
150
- - `raft.attachments.upload({ target, filename, bytes })` — small-file multipart
151
- upload (the target is resolved to a channel id first); files at or above the
152
- Server's direct-upload threshold are refused with a next action pointing at
153
- the upload-session routes. `download`, `comments`.
150
+ - `raft.attachments.upload({ target, filename, bytes })` — multipart below the
151
+ Server's direct-upload threshold, an upload session (presigned PUT with
152
+ `fetch`, then complete) at or above it, exactly as the CLI chooses. The
153
+ target is resolved to a channel id first. `download`, `comments`.
154
+ - `raft.actions.prepare({ target, action })` — post an action card
155
+ (`channel:create`, `channel:add_member`, `agent:create`, integration cards)
156
+ for a human to confirm; the human who clicks it executes it.
154
157
  - `raft.mentions.pending / execute / deliveries` — @mentions you sent that
155
158
  reached nobody, the notify/add recovery, and per-target delivery outcomes.
156
159
  - `raft.manual.get / search` — the Raft Manual for Agents; both need a short
@@ -285,12 +288,28 @@ request is sent.
285
288
 
286
289
  ### `client.routes` — every Agent API route, typed from the shared contract
287
290
 
288
- `client.routes.<resource>.<method>(params?, query?, body?)` exposes each route
291
+ `client.routes.<resource>.<method>({ params, query, body })` exposes each route
289
292
  in the Raft Agent API contract with request and response types derived from the
290
293
  same contract the Server validates. It is the SDK's code-level escape hatch and
291
- the guarantee that the SDK reaches every route the Raft CLI does; the higher-
292
- level `messages`, `events`, `channels`, `agent`, and profile helpers stay the
293
- recommended path for common work.
294
+ the guarantee that the SDK reaches every route the Raft CLI does; the
295
+ higher-level operations stay the recommended path for common work.
296
+
297
+ Every route takes **one named object** with only the parts it has. The types
298
+ are generated per route: a part the route does not have is a compile error, and
299
+ a required part (a body with required fields, a path param) is a required
300
+ property.
301
+
302
+ ```ts
303
+ await raft.routes.actions.prepare({ body: { target: "#ops", action } });
304
+ await raft.routes.messages.addReaction({ params: { msgId }, body: { emoji: "✅" } });
305
+ await raft.routes.server.info(); // no input
306
+ await raft.routes.request("actionPrepare", { body: { target: "#ops", action } }); // by route key
307
+ ```
308
+
309
+ For JavaScript callers without type checking, the same rules are enforced at
310
+ runtime: extra arguments or unknown keys are refused with
311
+ `request_contract_mismatch`, as is a missing required body, and nothing is sent.
312
+ `routes.describe(key)` shows which parts a route takes.
294
313
 
295
314
  ```ts
296
315
  const status = await raft.routes.pushWebhook.status();
@@ -7390,8 +7390,13 @@ function encodeQuery(routeKey, query) {
7390
7390
  return params;
7391
7391
  }
7392
7392
  function parseBody(routeKey, body) {
7393
- if (body === void 0) return void 0;
7394
7393
  const route = agentApiContract[routeKey];
7394
+ if (body === void 0) {
7395
+ if (!("body" in route.request)) return void 0;
7396
+ const schema = route.request.body;
7397
+ if (schema.safeParse(void 0).success || schema.safeParse({}).success) return void 0;
7398
+ return failure$3(routeKey, "request_contract_mismatch", `Agent API ${route.key} requires a request body`);
7399
+ }
7395
7400
  try {
7396
7401
  return "body" in route.request ? route.request.body.parse(body) : void 0;
7397
7402
  } catch (cause) {
@@ -7496,7 +7501,19 @@ function createAgentApiRawClient(transport, options = {}) {
7496
7501
  const routeKey = route.key;
7497
7502
  const { resource, method } = route.client;
7498
7503
  const resourceClient = client[resource] ??= {};
7499
- resourceClient[method] = (...args) => requestAgentApiRawRoute(transport, routeKey, requestOptionsFromMethodArgs(routeKey, pathPrefix, args));
7504
+ const expectedArgs = [
7505
+ "params",
7506
+ "query",
7507
+ "body"
7508
+ ].filter((part) => part in route.request).length;
7509
+ resourceClient[method] = (...args) => {
7510
+ if (args.length > expectedArgs) return Promise.resolve(failure$3(routeKey, "request_contract_mismatch", `Agent API ${route.key} takes ${expectedArgs} argument${expectedArgs === 1 ? "" : "s"} (${[
7511
+ "params",
7512
+ "query",
7513
+ "body"
7514
+ ].filter((part) => part in route.request).join(", ") || "none"}); got ${args.length}`));
7515
+ return requestAgentApiRawRoute(transport, routeKey, requestOptionsFromMethodArgs(routeKey, pathPrefix, args));
7516
+ };
7500
7517
  }
7501
7518
  return client;
7502
7519
  }
@@ -9887,7 +9904,7 @@ function clampAttempts(value) {
9887
9904
  * The split is decided per route from `AGENT_API_ROUTE_META`, never by the
9888
9905
  * caller and never by the HTTP method.
9889
9906
  */
9890
- function createRaftRoutes(options) {
9907
+ function createRaftRouteClient(options) {
9891
9908
  const base = {
9892
9909
  baseUrl: options.serverUrl,
9893
9910
  fetch: options.fetch,
@@ -9914,12 +9931,61 @@ function createRaftRoutes(options) {
9914
9931
  ...merged,
9915
9932
  request: (routeKey, requestOptions) => {
9916
9933
  return (ROUTE_INFO[routeKey].retryPolicy === "retry" ? retrying : single).request(routeKey, requestOptions);
9917
- },
9934
+ }
9935
+ };
9936
+ }
9937
+ const ROUTE_PARTS = [
9938
+ "params",
9939
+ "query",
9940
+ "body"
9941
+ ];
9942
+ function inputMismatch(routeKey, message) {
9943
+ return Promise.resolve({
9944
+ ok: false,
9945
+ routeKey,
9946
+ error: {
9947
+ kind: "validation",
9948
+ reason: "request_contract_mismatch",
9949
+ message: `Agent API ${routeKey} ${message}`
9950
+ }
9951
+ });
9952
+ }
9953
+ /**
9954
+ * The public routes layer: one named-object call shape for every route,
9955
+ * `routes.<resource>.<method>({ params, query, body })`. The types reject a
9956
+ * missing required part or a part the route does not have; for callers
9957
+ * without type checking, the same rules are enforced at runtime and nothing
9958
+ * is sent on a mismatch.
9959
+ */
9960
+ function raftRoutesFromClient(client) {
9961
+ const call = (routeKey, args) => {
9962
+ if (args.length > 1) return inputMismatch(routeKey, `takes one { params, query, body } object; got ${args.length} arguments`);
9963
+ const input = args[0];
9964
+ if (input !== void 0 && (input === null || typeof input !== "object" || Array.isArray(input))) return inputMismatch(routeKey, "takes one { params, query, body } object");
9965
+ const route = agentApiContract[routeKey];
9966
+ const declared = ROUTE_PARTS.filter((part) => part in route.request);
9967
+ const unknown = (input === void 0 ? [] : Object.keys(input)).filter((key) => !declared.includes(key));
9968
+ if (unknown.length > 0) return inputMismatch(routeKey, `has no ${unknown.join(", ")} (it takes ${declared.join(", ") || "no input"})`);
9969
+ return client.request(routeKey, input ?? {});
9970
+ };
9971
+ const methods = {};
9972
+ for (const route of Object.values(agentApiContract)) {
9973
+ const key = route.key;
9974
+ methods[route.client.resource] ??= {};
9975
+ methods[route.client.resource][route.client.method] = (...args) => call(key, args);
9976
+ }
9977
+ return {
9978
+ ...methods,
9979
+ request: (routeKey, ...rest) => call(routeKey, rest),
9918
9980
  manifestVersion: AGENT_API_MANIFEST_VERSION,
9919
9981
  describe: describeRaftRoute,
9920
9982
  list: listRaftRoutes
9921
9983
  };
9922
9984
  }
9985
+ /** Build the public routes layer directly (convenience over createRaftRouteClient + raftRoutesFromClient). */
9986
+ function createRaftRoutes(options) {
9987
+ return raftRoutesFromClient(createRaftRouteClient(options));
9988
+ }
9923
9989
  //#endregion
9924
9990
  //#region src/context.ts
9925
9991
  const projection = object({
@@ -12929,10 +12995,9 @@ function inferAttachmentMimeType(filename, bytes, explicit) {
12929
12995
  return sniffMimeType(bytes) ?? FILENAME_MIME_MAP[ext] ?? "application/octet-stream";
12930
12996
  }
12931
12997
  /**
12932
- * Small-file multipart upload (`POST /upload`). Files at or above the Server's
12933
- * direct-upload threshold need the upload-session routes (`routes.attachments.
12934
- * createUploadSession` …), which this operation reports as `UNAVAILABLE` with a
12935
- * next action rather than attempting silently.
12998
+ * Upload bytes into a conversation, choosing the path the CLI would: multipart
12999
+ * `POST /upload` below the Server's direct-upload threshold, an upload session
13000
+ * (create → PUT to a presigned URL → complete) at or above it.
12936
13001
  */
12937
13002
  async function uploadAttachment(client, transport, request) {
12938
13003
  if (!request.target?.trim() || !request.filename?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target and a filename are required to upload." }));
@@ -12942,15 +13007,11 @@ async function uploadAttachment(client, transport, request) {
12942
13007
  const maxBytes = capability.ok ? capability.data.maxBytes : AGENT_ATTACHMENT_UPLOAD_FALLBACK_MAX_BYTES;
12943
13008
  const threshold = capability.ok && capability.data.directUploadEnabled ? capability.data.directUploadThresholdBytes : null;
12944
13009
  if (request.bytes.byteLength > maxBytes) return failureOutcome(opError("INVALID_REQUEST", { message: `Attachment is ${request.bytes.byteLength} bytes; the Server's maximum is ${maxBytes}.` }));
12945
- if (threshold !== null && request.bytes.byteLength >= threshold) return failureOutcome(opError("UNAVAILABLE", {
12946
- message: `Files of ${threshold} bytes or more use the direct-upload session routes, which this operation does not drive yet.`,
12947
- nextAction: "Use routes.attachments.createUploadSession / completeUploadSession, or upload a smaller file.",
12948
- retryable: false
12949
- }));
12950
13010
  const resolved = await client.channels.resolve({ target: request.target });
12951
13011
  if (!resolved.ok) return failureFromClientResult(resolved);
12952
13012
  const channelId = resolved.data.channelId;
12953
13013
  const mimeType = inferAttachmentMimeType(request.filename, request.bytes, request.mimeType);
13014
+ if (threshold !== null && request.bytes.byteLength >= threshold) return uploadThroughSession(client, transport, request, channelId, mimeType);
12954
13015
  const copy = new Uint8Array(request.bytes.byteLength);
12955
13016
  copy.set(request.bytes);
12956
13017
  const form = new FormData();
@@ -12990,20 +13051,22 @@ async function uploadAttachment(client, transport, request) {
12990
13051
  }
12991
13052
  const parsed = agentApiContract.attachmentUpload.response.body.safeParse(body);
12992
13053
  if (!parsed.success) return failureOutcome(opError("INVALID_RESPONSE"));
12993
- const data = parsed.data;
13054
+ return uploadedOutcome(request.target, parsed.data, mimeType);
13055
+ }
13056
+ function uploadedOutcome(target, data, mimeType) {
12994
13057
  return {
12995
13058
  ok: true,
12996
13059
  state: "uploaded",
12997
13060
  data: {
12998
13061
  ...data,
12999
- target: request.target,
13062
+ target,
13000
13063
  mimeType: data.mimeType ?? mimeType
13001
13064
  },
13002
13065
  next: {
13003
13066
  kind: "send_with_attachment",
13004
- command: `raft message send --target "${request.target}" --attachment-id ${data.id}`,
13067
+ command: `raft message send --target "${target}" --attachment-id ${data.id}`,
13005
13068
  args: {
13006
- target: request.target,
13069
+ target,
13007
13070
  attachmentIds: [data.id]
13008
13071
  },
13009
13072
  why: "The upload alone posts nothing; send a message that links the attachment id."
@@ -13011,6 +13074,69 @@ async function uploadAttachment(client, transport, request) {
13011
13074
  text: formatAgentAttachmentUploaded(data)
13012
13075
  };
13013
13076
  }
13077
+ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
13078
+ /**
13079
+ * Direct upload for files at or above the Server's threshold, mirroring the
13080
+ * CLI: create a session, PUT the bytes to the presigned URL (fetch; one retry
13081
+ * when the object store may have committed), then complete, which the Server
13082
+ * verifies against the object store. A PUT that definitely failed cancels the
13083
+ * session; a PUT whose outcome is unknown leaves it for completion to verify.
13084
+ */
13085
+ async function uploadThroughSession(client, transport, request, channelId, mimeType) {
13086
+ const created = await client.attachments.createUploadSession({
13087
+ channelId,
13088
+ filename: request.filename,
13089
+ mimeType,
13090
+ sizeBytes: request.bytes.byteLength,
13091
+ clientRequestId: crypto.randomUUID()
13092
+ });
13093
+ if (!created.ok) return failureFromClientResult(created);
13094
+ const { uploadId, upload } = created.data;
13095
+ const body = new Uint8Array(request.bytes.byteLength);
13096
+ body.set(request.bytes);
13097
+ let definitelyFailed = null;
13098
+ for (let attempt = 0; attempt < 2; attempt += 1) {
13099
+ let response;
13100
+ try {
13101
+ response = await transport.fetch(upload.url, {
13102
+ method: upload.method ?? "PUT",
13103
+ headers: { ...upload.headers },
13104
+ body: body.buffer,
13105
+ redirect: "error"
13106
+ });
13107
+ } catch {
13108
+ if (attempt === 0) continue;
13109
+ break;
13110
+ }
13111
+ if (response.ok || response.status === 412) {
13112
+ definitelyFailed = null;
13113
+ break;
13114
+ }
13115
+ const mayExist = response.status === 408 || response.status === 429 || response.status >= 500;
13116
+ if (mayExist && attempt === 0) continue;
13117
+ if (!mayExist) definitelyFailed = response.status;
13118
+ break;
13119
+ }
13120
+ if (definitelyFailed !== null) {
13121
+ await client.attachments.cancelUploadSession({ uploadId }).catch(() => void 0);
13122
+ return failureOutcome(opError("HTTP_ERROR", {
13123
+ message: `Direct object upload failed with HTTP ${definitelyFailed}; the upload session was cancelled.`,
13124
+ status: definitelyFailed,
13125
+ nextAction: "Retry the upload; nothing was attached."
13126
+ }));
13127
+ }
13128
+ for (let attempt = 0; attempt < 3; attempt += 1) {
13129
+ const completed = await client.attachments.completeUploadSession({ uploadId });
13130
+ if (completed.ok) {
13131
+ const attachment = completed.data.attachment;
13132
+ return uploadedOutcome(request.target, attachment, mimeType);
13133
+ }
13134
+ const code = completed.error.kind === "http" ? completed.error.errorCode : null;
13135
+ if (!(code === "UPLOAD_OBJECT_NOT_FOUND" || code === "UPLOAD_VERIFICATION_IN_PROGRESS") || attempt === 2) return failureFromClientResult(completed);
13136
+ await wait(250 * (attempt + 1));
13137
+ }
13138
+ return failureOutcome(opError("UNAVAILABLE", { message: "Direct upload completion ended without a terminal response." }));
13139
+ }
13014
13140
  async function downloadAttachment(client, request) {
13015
13141
  if (!request.attachmentId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "An attachment id is required." }));
13016
13142
  const result = await client.attachments.download({ attachmentId: request.attachmentId });
@@ -13772,6 +13898,37 @@ var RaftStateSession = class {
13772
13898
  }
13773
13899
  };
13774
13900
  //#endregion
13901
+ //#region ../shared/src/agentOps/actions.ts
13902
+ /** Text is the CLI's `raft action prepare` output. */
13903
+ function formatAgentActionCardPosted(target, messageId) {
13904
+ const shortId = messageId ? messageId.slice(0, 8) : null;
13905
+ return shortId ? `Action card posted to ${target} as message ${messageId} (short ${shortId}). The human can click the action verb to commit.\n` : `Action card posted to ${target}.\n`;
13906
+ }
13907
+ async function prepareActionCard(client, request) {
13908
+ if (!request?.target?.trim() || !request.action) return failureOutcome(opError("INVALID_REQUEST", { message: "A target and an action are required to prepare a card." }));
13909
+ const result = await client.actions.prepare(request);
13910
+ if (!result.ok) return failureFromClientResult(result);
13911
+ const card = {
13912
+ target: request.target,
13913
+ messageId: result.data.messageId
13914
+ };
13915
+ return {
13916
+ ok: true,
13917
+ state: "prepared",
13918
+ data: card,
13919
+ next: {
13920
+ kind: "await_confirmation",
13921
+ command: `raft message read --target "${request.target}" --around ${card.messageId.slice(0, 8)}`,
13922
+ args: {
13923
+ target: request.target,
13924
+ messageId: card.messageId
13925
+ },
13926
+ why: "A human must click the card to commit it; its outcome arrives in your inbox."
13927
+ },
13928
+ text: formatAgentActionCardPosted(request.target, card.messageId)
13929
+ };
13930
+ }
13931
+ //#endregion
13775
13932
  //#region src/raft.ts
13776
13933
  function createRaft(options) {
13777
13934
  const serverUrl = requireServerUrl(options.serverUrl);
@@ -13785,7 +13942,7 @@ function createRaft(options) {
13785
13942
  if (outcome.ok) await session.save();
13786
13943
  return outcome;
13787
13944
  };
13788
- const routes = createRaftRoutes({
13945
+ const api = createRaftRouteClient({
13789
13946
  serverUrl,
13790
13947
  fetch: options.fetch,
13791
13948
  headers: options.headers,
@@ -13793,6 +13950,7 @@ function createRaft(options) {
13793
13950
  readAttempts: options.retry?.attempts,
13794
13951
  beforeRequest: options.throttle?.beforeRequest
13795
13952
  });
13953
+ const routes = raftRoutesFromClient(api);
13796
13954
  const inboxApi = createAgentApiClient({ fetch: {
13797
13955
  baseUrl: serverUrl,
13798
13956
  fetch: (input, init) => (options.fetch ?? fetch)(input, {
@@ -13813,7 +13971,7 @@ function createRaft(options) {
13813
13971
  const sendWithState = (request) => withState(async () => {
13814
13972
  const contentHash = await hashRaftSendContent(request.target, request.content ?? "", request.attachmentIds ?? []);
13815
13973
  const pending = request.idempotencyKey ? void 0 : session.findContinuation(request.target, contentHash);
13816
- const outcome = await sendMessage(routes, pending ? {
13974
+ const outcome = await sendMessage(api, pending ? {
13817
13975
  ...request,
13818
13976
  idempotencyKey: pending.idempotencyKey
13819
13977
  } : request, frontier);
@@ -13827,13 +13985,13 @@ function createRaft(options) {
13827
13985
  return outcome;
13828
13986
  });
13829
13987
  return {
13830
- identity: { whoami: () => getRaftContext(routes) },
13988
+ identity: { whoami: () => getRaftContext(api) },
13831
13989
  wake: {
13832
13990
  verifyNotice: (input) => verifyInboxNotice(input),
13833
13991
  webhook: {
13834
- status: () => webhookStatus(routes),
13835
- register: (request) => registerWebhook(routes, request),
13836
- unregister: () => unregisterWebhook(routes)
13992
+ status: () => webhookStatus(api),
13993
+ register: (request) => registerWebhook(api, request),
13994
+ unregister: () => unregisterWebhook(api)
13837
13995
  }
13838
13996
  },
13839
13997
  inbox: {
@@ -13871,11 +14029,11 @@ function createRaft(options) {
13871
14029
  await session.save();
13872
14030
  });
13873
14031
  })(),
13874
- list: (request) => listInbox(routes, request)
14032
+ list: (request) => listInbox(api, request)
13875
14033
  },
13876
14034
  messages: {
13877
14035
  read: (request) => withState(async () => {
13878
- const outcome = await readHistory(routes, request, frontier);
14036
+ const outcome = await readHistory(api, request, frontier);
13879
14037
  if (outcome.ok) session.markDirty();
13880
14038
  return outcome;
13881
14039
  }),
@@ -13884,57 +14042,58 @@ function createRaft(options) {
13884
14042
  ...request,
13885
14043
  target: message.target
13886
14044
  }),
13887
- search: (request) => searchMessages(routes, request),
13888
- resolve: (request) => resolveMessage(routes, request),
13889
- react: (request) => reactToMessage(routes, request, "add"),
13890
- unreact: (request) => reactToMessage(routes, request, "remove")
14045
+ search: (request) => searchMessages(api, request),
14046
+ resolve: (request) => resolveMessage(api, request),
14047
+ react: (request) => reactToMessage(api, request, "add"),
14048
+ unreact: (request) => reactToMessage(api, request, "remove")
13891
14049
  },
13892
14050
  attachments: {
13893
- upload: (request) => uploadAttachment(routes, {
14051
+ upload: (request) => uploadAttachment(api, {
13894
14052
  serverUrl,
13895
14053
  fetch: options.fetch ?? fetch,
13896
14054
  headers: Object.fromEntries(new Headers(options.headers)),
13897
14055
  authorization
13898
14056
  }, request),
13899
- download: (request) => downloadAttachment(routes, request),
13900
- comments: (request) => attachmentComments(routes, request)
14057
+ download: (request) => downloadAttachment(api, request),
14058
+ comments: (request) => attachmentComments(api, request)
13901
14059
  },
13902
14060
  mentions: {
13903
- pending: (request) => pendingMentionActions(routes, request),
13904
- execute: (request) => executeMentionAction(routes, request),
13905
- deliveries: (request) => senderMentionDeliveries(routes, request)
14061
+ pending: (request) => pendingMentionActions(api, request),
14062
+ execute: (request) => executeMentionAction(api, request),
14063
+ deliveries: (request) => senderMentionDeliveries(api, request)
13906
14064
  },
14065
+ actions: { prepare: (request) => prepareActionCard(api, request) },
13907
14066
  manual: {
13908
- get: (request) => getManualTopic(routes, request),
13909
- search: (request) => searchManual(routes, request)
14067
+ get: (request) => getManualTopic(api, request),
14068
+ search: (request) => searchManual(api, request)
13910
14069
  },
13911
14070
  tasks: {
13912
- claim: (request) => claimTasks(routes, request),
13913
- list: (request) => listTasks(routes, request),
13914
- create: (request) => createTasks(routes, request),
13915
- unclaim: (request) => unclaimTask(routes, request),
13916
- assign: (request) => assignTask(routes, request),
13917
- updateStatus: (request) => updateTaskStatus(routes, request),
13918
- amend: (request) => amendTask(routes, request),
13919
- history: (request) => taskHistory(routes, request),
13920
- convert: (request) => convertMessageToTask(routes, request),
13921
- delete: (request) => deleteTask(routes, request)
14071
+ claim: (request) => claimTasks(api, request),
14072
+ list: (request) => listTasks(api, request),
14073
+ create: (request) => createTasks(api, request),
14074
+ unclaim: (request) => unclaimTask(api, request),
14075
+ assign: (request) => assignTask(api, request),
14076
+ updateStatus: (request) => updateTaskStatus(api, request),
14077
+ amend: (request) => amendTask(api, request),
14078
+ history: (request) => taskHistory(api, request),
14079
+ convert: (request) => convertMessageToTask(api, request),
14080
+ delete: (request) => deleteTask(api, request)
13922
14081
  },
13923
14082
  channels: {
13924
- join: (request) => joinChannel(routes, request),
13925
- leave: (request) => leaveChannel(routes, request),
13926
- mute: (request) => muteChannel(routes, request),
13927
- unmute: (request) => unmuteChannel(routes, request),
13928
- members: (request) => channelMembers(routes, request)
14083
+ join: (request) => joinChannel(api, request),
14084
+ leave: (request) => leaveChannel(api, request),
14085
+ mute: (request) => muteChannel(api, request),
14086
+ unmute: (request) => unmuteChannel(api, request),
14087
+ members: (request) => channelMembers(api, request)
13929
14088
  },
13930
14089
  threads: {
13931
- list: () => listThreads(routes),
13932
- unfollow: (request) => unfollowThread(routes, request)
14090
+ list: () => listThreads(api),
14091
+ unfollow: (request) => unfollowThread(api, request)
13933
14092
  },
13934
- server: { info: (request) => serverInfo(routes, request) },
14093
+ server: { info: (request) => serverInfo(api, request) },
13935
14094
  profile: {
13936
- show: (request) => showProfile(routes, request),
13937
- update: (request) => updateProfile(routes, request)
14095
+ show: (request) => showProfile(api, request),
14096
+ update: (request) => updateProfile(api, request)
13938
14097
  },
13939
14098
  frontier,
13940
14099
  state: {
package/dist/esm/index.js CHANGED
@@ -7389,8 +7389,13 @@ function encodeQuery(routeKey, query) {
7389
7389
  return params;
7390
7390
  }
7391
7391
  function parseBody(routeKey, body) {
7392
- if (body === void 0) return void 0;
7393
7392
  const route = agentApiContract[routeKey];
7393
+ if (body === void 0) {
7394
+ if (!("body" in route.request)) return void 0;
7395
+ const schema = route.request.body;
7396
+ if (schema.safeParse(void 0).success || schema.safeParse({}).success) return void 0;
7397
+ return failure$3(routeKey, "request_contract_mismatch", `Agent API ${route.key} requires a request body`);
7398
+ }
7394
7399
  try {
7395
7400
  return "body" in route.request ? route.request.body.parse(body) : void 0;
7396
7401
  } catch (cause) {
@@ -7495,7 +7500,19 @@ function createAgentApiRawClient(transport, options = {}) {
7495
7500
  const routeKey = route.key;
7496
7501
  const { resource, method } = route.client;
7497
7502
  const resourceClient = client[resource] ??= {};
7498
- resourceClient[method] = (...args) => requestAgentApiRawRoute(transport, routeKey, requestOptionsFromMethodArgs(routeKey, pathPrefix, args));
7503
+ const expectedArgs = [
7504
+ "params",
7505
+ "query",
7506
+ "body"
7507
+ ].filter((part) => part in route.request).length;
7508
+ resourceClient[method] = (...args) => {
7509
+ if (args.length > expectedArgs) return Promise.resolve(failure$3(routeKey, "request_contract_mismatch", `Agent API ${route.key} takes ${expectedArgs} argument${expectedArgs === 1 ? "" : "s"} (${[
7510
+ "params",
7511
+ "query",
7512
+ "body"
7513
+ ].filter((part) => part in route.request).join(", ") || "none"}); got ${args.length}`));
7514
+ return requestAgentApiRawRoute(transport, routeKey, requestOptionsFromMethodArgs(routeKey, pathPrefix, args));
7515
+ };
7499
7516
  }
7500
7517
  return client;
7501
7518
  }
@@ -9886,7 +9903,7 @@ function clampAttempts(value) {
9886
9903
  * The split is decided per route from `AGENT_API_ROUTE_META`, never by the
9887
9904
  * caller and never by the HTTP method.
9888
9905
  */
9889
- function createRaftRoutes(options) {
9906
+ function createRaftRouteClient(options) {
9890
9907
  const base = {
9891
9908
  baseUrl: options.serverUrl,
9892
9909
  fetch: options.fetch,
@@ -9913,12 +9930,61 @@ function createRaftRoutes(options) {
9913
9930
  ...merged,
9914
9931
  request: (routeKey, requestOptions) => {
9915
9932
  return (ROUTE_INFO[routeKey].retryPolicy === "retry" ? retrying : single).request(routeKey, requestOptions);
9916
- },
9933
+ }
9934
+ };
9935
+ }
9936
+ const ROUTE_PARTS = [
9937
+ "params",
9938
+ "query",
9939
+ "body"
9940
+ ];
9941
+ function inputMismatch(routeKey, message) {
9942
+ return Promise.resolve({
9943
+ ok: false,
9944
+ routeKey,
9945
+ error: {
9946
+ kind: "validation",
9947
+ reason: "request_contract_mismatch",
9948
+ message: `Agent API ${routeKey} ${message}`
9949
+ }
9950
+ });
9951
+ }
9952
+ /**
9953
+ * The public routes layer: one named-object call shape for every route,
9954
+ * `routes.<resource>.<method>({ params, query, body })`. The types reject a
9955
+ * missing required part or a part the route does not have; for callers
9956
+ * without type checking, the same rules are enforced at runtime and nothing
9957
+ * is sent on a mismatch.
9958
+ */
9959
+ function raftRoutesFromClient(client) {
9960
+ const call = (routeKey, args) => {
9961
+ if (args.length > 1) return inputMismatch(routeKey, `takes one { params, query, body } object; got ${args.length} arguments`);
9962
+ const input = args[0];
9963
+ if (input !== void 0 && (input === null || typeof input !== "object" || Array.isArray(input))) return inputMismatch(routeKey, "takes one { params, query, body } object");
9964
+ const route = agentApiContract[routeKey];
9965
+ const declared = ROUTE_PARTS.filter((part) => part in route.request);
9966
+ const unknown = (input === void 0 ? [] : Object.keys(input)).filter((key) => !declared.includes(key));
9967
+ if (unknown.length > 0) return inputMismatch(routeKey, `has no ${unknown.join(", ")} (it takes ${declared.join(", ") || "no input"})`);
9968
+ return client.request(routeKey, input ?? {});
9969
+ };
9970
+ const methods = {};
9971
+ for (const route of Object.values(agentApiContract)) {
9972
+ const key = route.key;
9973
+ methods[route.client.resource] ??= {};
9974
+ methods[route.client.resource][route.client.method] = (...args) => call(key, args);
9975
+ }
9976
+ return {
9977
+ ...methods,
9978
+ request: (routeKey, ...rest) => call(routeKey, rest),
9917
9979
  manifestVersion: AGENT_API_MANIFEST_VERSION,
9918
9980
  describe: describeRaftRoute,
9919
9981
  list: listRaftRoutes
9920
9982
  };
9921
9983
  }
9984
+ /** Build the public routes layer directly (convenience over createRaftRouteClient + raftRoutesFromClient). */
9985
+ function createRaftRoutes(options) {
9986
+ return raftRoutesFromClient(createRaftRouteClient(options));
9987
+ }
9922
9988
  //#endregion
9923
9989
  //#region src/context.ts
9924
9990
  const projection = object({
@@ -12928,10 +12994,9 @@ function inferAttachmentMimeType(filename, bytes, explicit) {
12928
12994
  return sniffMimeType(bytes) ?? FILENAME_MIME_MAP[ext] ?? "application/octet-stream";
12929
12995
  }
12930
12996
  /**
12931
- * Small-file multipart upload (`POST /upload`). Files at or above the Server's
12932
- * direct-upload threshold need the upload-session routes (`routes.attachments.
12933
- * createUploadSession` …), which this operation reports as `UNAVAILABLE` with a
12934
- * next action rather than attempting silently.
12997
+ * Upload bytes into a conversation, choosing the path the CLI would: multipart
12998
+ * `POST /upload` below the Server's direct-upload threshold, an upload session
12999
+ * (create → PUT to a presigned URL → complete) at or above it.
12935
13000
  */
12936
13001
  async function uploadAttachment(client, transport, request) {
12937
13002
  if (!request.target?.trim() || !request.filename?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target and a filename are required to upload." }));
@@ -12941,15 +13006,11 @@ async function uploadAttachment(client, transport, request) {
12941
13006
  const maxBytes = capability.ok ? capability.data.maxBytes : AGENT_ATTACHMENT_UPLOAD_FALLBACK_MAX_BYTES;
12942
13007
  const threshold = capability.ok && capability.data.directUploadEnabled ? capability.data.directUploadThresholdBytes : null;
12943
13008
  if (request.bytes.byteLength > maxBytes) return failureOutcome(opError("INVALID_REQUEST", { message: `Attachment is ${request.bytes.byteLength} bytes; the Server's maximum is ${maxBytes}.` }));
12944
- if (threshold !== null && request.bytes.byteLength >= threshold) return failureOutcome(opError("UNAVAILABLE", {
12945
- message: `Files of ${threshold} bytes or more use the direct-upload session routes, which this operation does not drive yet.`,
12946
- nextAction: "Use routes.attachments.createUploadSession / completeUploadSession, or upload a smaller file.",
12947
- retryable: false
12948
- }));
12949
13009
  const resolved = await client.channels.resolve({ target: request.target });
12950
13010
  if (!resolved.ok) return failureFromClientResult(resolved);
12951
13011
  const channelId = resolved.data.channelId;
12952
13012
  const mimeType = inferAttachmentMimeType(request.filename, request.bytes, request.mimeType);
13013
+ if (threshold !== null && request.bytes.byteLength >= threshold) return uploadThroughSession(client, transport, request, channelId, mimeType);
12953
13014
  const copy = new Uint8Array(request.bytes.byteLength);
12954
13015
  copy.set(request.bytes);
12955
13016
  const form = new FormData();
@@ -12989,20 +13050,22 @@ async function uploadAttachment(client, transport, request) {
12989
13050
  }
12990
13051
  const parsed = agentApiContract.attachmentUpload.response.body.safeParse(body);
12991
13052
  if (!parsed.success) return failureOutcome(opError("INVALID_RESPONSE"));
12992
- const data = parsed.data;
13053
+ return uploadedOutcome(request.target, parsed.data, mimeType);
13054
+ }
13055
+ function uploadedOutcome(target, data, mimeType) {
12993
13056
  return {
12994
13057
  ok: true,
12995
13058
  state: "uploaded",
12996
13059
  data: {
12997
13060
  ...data,
12998
- target: request.target,
13061
+ target,
12999
13062
  mimeType: data.mimeType ?? mimeType
13000
13063
  },
13001
13064
  next: {
13002
13065
  kind: "send_with_attachment",
13003
- command: `raft message send --target "${request.target}" --attachment-id ${data.id}`,
13066
+ command: `raft message send --target "${target}" --attachment-id ${data.id}`,
13004
13067
  args: {
13005
- target: request.target,
13068
+ target,
13006
13069
  attachmentIds: [data.id]
13007
13070
  },
13008
13071
  why: "The upload alone posts nothing; send a message that links the attachment id."
@@ -13010,6 +13073,69 @@ async function uploadAttachment(client, transport, request) {
13010
13073
  text: formatAgentAttachmentUploaded(data)
13011
13074
  };
13012
13075
  }
13076
+ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
13077
+ /**
13078
+ * Direct upload for files at or above the Server's threshold, mirroring the
13079
+ * CLI: create a session, PUT the bytes to the presigned URL (fetch; one retry
13080
+ * when the object store may have committed), then complete, which the Server
13081
+ * verifies against the object store. A PUT that definitely failed cancels the
13082
+ * session; a PUT whose outcome is unknown leaves it for completion to verify.
13083
+ */
13084
+ async function uploadThroughSession(client, transport, request, channelId, mimeType) {
13085
+ const created = await client.attachments.createUploadSession({
13086
+ channelId,
13087
+ filename: request.filename,
13088
+ mimeType,
13089
+ sizeBytes: request.bytes.byteLength,
13090
+ clientRequestId: crypto.randomUUID()
13091
+ });
13092
+ if (!created.ok) return failureFromClientResult(created);
13093
+ const { uploadId, upload } = created.data;
13094
+ const body = new Uint8Array(request.bytes.byteLength);
13095
+ body.set(request.bytes);
13096
+ let definitelyFailed = null;
13097
+ for (let attempt = 0; attempt < 2; attempt += 1) {
13098
+ let response;
13099
+ try {
13100
+ response = await transport.fetch(upload.url, {
13101
+ method: upload.method ?? "PUT",
13102
+ headers: { ...upload.headers },
13103
+ body: body.buffer,
13104
+ redirect: "error"
13105
+ });
13106
+ } catch {
13107
+ if (attempt === 0) continue;
13108
+ break;
13109
+ }
13110
+ if (response.ok || response.status === 412) {
13111
+ definitelyFailed = null;
13112
+ break;
13113
+ }
13114
+ const mayExist = response.status === 408 || response.status === 429 || response.status >= 500;
13115
+ if (mayExist && attempt === 0) continue;
13116
+ if (!mayExist) definitelyFailed = response.status;
13117
+ break;
13118
+ }
13119
+ if (definitelyFailed !== null) {
13120
+ await client.attachments.cancelUploadSession({ uploadId }).catch(() => void 0);
13121
+ return failureOutcome(opError("HTTP_ERROR", {
13122
+ message: `Direct object upload failed with HTTP ${definitelyFailed}; the upload session was cancelled.`,
13123
+ status: definitelyFailed,
13124
+ nextAction: "Retry the upload; nothing was attached."
13125
+ }));
13126
+ }
13127
+ for (let attempt = 0; attempt < 3; attempt += 1) {
13128
+ const completed = await client.attachments.completeUploadSession({ uploadId });
13129
+ if (completed.ok) {
13130
+ const attachment = completed.data.attachment;
13131
+ return uploadedOutcome(request.target, attachment, mimeType);
13132
+ }
13133
+ const code = completed.error.kind === "http" ? completed.error.errorCode : null;
13134
+ if (!(code === "UPLOAD_OBJECT_NOT_FOUND" || code === "UPLOAD_VERIFICATION_IN_PROGRESS") || attempt === 2) return failureFromClientResult(completed);
13135
+ await wait(250 * (attempt + 1));
13136
+ }
13137
+ return failureOutcome(opError("UNAVAILABLE", { message: "Direct upload completion ended without a terminal response." }));
13138
+ }
13013
13139
  async function downloadAttachment(client, request) {
13014
13140
  if (!request.attachmentId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "An attachment id is required." }));
13015
13141
  const result = await client.attachments.download({ attachmentId: request.attachmentId });
@@ -13771,6 +13897,37 @@ var RaftStateSession = class {
13771
13897
  }
13772
13898
  };
13773
13899
  //#endregion
13900
+ //#region ../shared/src/agentOps/actions.ts
13901
+ /** Text is the CLI's `raft action prepare` output. */
13902
+ function formatAgentActionCardPosted(target, messageId) {
13903
+ const shortId = messageId ? messageId.slice(0, 8) : null;
13904
+ return shortId ? `Action card posted to ${target} as message ${messageId} (short ${shortId}). The human can click the action verb to commit.\n` : `Action card posted to ${target}.\n`;
13905
+ }
13906
+ async function prepareActionCard(client, request) {
13907
+ if (!request?.target?.trim() || !request.action) return failureOutcome(opError("INVALID_REQUEST", { message: "A target and an action are required to prepare a card." }));
13908
+ const result = await client.actions.prepare(request);
13909
+ if (!result.ok) return failureFromClientResult(result);
13910
+ const card = {
13911
+ target: request.target,
13912
+ messageId: result.data.messageId
13913
+ };
13914
+ return {
13915
+ ok: true,
13916
+ state: "prepared",
13917
+ data: card,
13918
+ next: {
13919
+ kind: "await_confirmation",
13920
+ command: `raft message read --target "${request.target}" --around ${card.messageId.slice(0, 8)}`,
13921
+ args: {
13922
+ target: request.target,
13923
+ messageId: card.messageId
13924
+ },
13925
+ why: "A human must click the card to commit it; its outcome arrives in your inbox."
13926
+ },
13927
+ text: formatAgentActionCardPosted(request.target, card.messageId)
13928
+ };
13929
+ }
13930
+ //#endregion
13774
13931
  //#region src/raft.ts
13775
13932
  function createRaft(options) {
13776
13933
  const serverUrl = requireServerUrl(options.serverUrl);
@@ -13784,7 +13941,7 @@ function createRaft(options) {
13784
13941
  if (outcome.ok) await session.save();
13785
13942
  return outcome;
13786
13943
  };
13787
- const routes = createRaftRoutes({
13944
+ const api = createRaftRouteClient({
13788
13945
  serverUrl,
13789
13946
  fetch: options.fetch,
13790
13947
  headers: options.headers,
@@ -13792,6 +13949,7 @@ function createRaft(options) {
13792
13949
  readAttempts: options.retry?.attempts,
13793
13950
  beforeRequest: options.throttle?.beforeRequest
13794
13951
  });
13952
+ const routes = raftRoutesFromClient(api);
13795
13953
  const inboxApi = createAgentApiClient({ fetch: {
13796
13954
  baseUrl: serverUrl,
13797
13955
  fetch: (input, init) => (options.fetch ?? fetch)(input, {
@@ -13812,7 +13970,7 @@ function createRaft(options) {
13812
13970
  const sendWithState = (request) => withState(async () => {
13813
13971
  const contentHash = await hashRaftSendContent(request.target, request.content ?? "", request.attachmentIds ?? []);
13814
13972
  const pending = request.idempotencyKey ? void 0 : session.findContinuation(request.target, contentHash);
13815
- const outcome = await sendMessage(routes, pending ? {
13973
+ const outcome = await sendMessage(api, pending ? {
13816
13974
  ...request,
13817
13975
  idempotencyKey: pending.idempotencyKey
13818
13976
  } : request, frontier);
@@ -13826,13 +13984,13 @@ function createRaft(options) {
13826
13984
  return outcome;
13827
13985
  });
13828
13986
  return {
13829
- identity: { whoami: () => getRaftContext(routes) },
13987
+ identity: { whoami: () => getRaftContext(api) },
13830
13988
  wake: {
13831
13989
  verifyNotice: (input) => verifyInboxNotice(input),
13832
13990
  webhook: {
13833
- status: () => webhookStatus(routes),
13834
- register: (request) => registerWebhook(routes, request),
13835
- unregister: () => unregisterWebhook(routes)
13991
+ status: () => webhookStatus(api),
13992
+ register: (request) => registerWebhook(api, request),
13993
+ unregister: () => unregisterWebhook(api)
13836
13994
  }
13837
13995
  },
13838
13996
  inbox: {
@@ -13870,11 +14028,11 @@ function createRaft(options) {
13870
14028
  await session.save();
13871
14029
  });
13872
14030
  })(),
13873
- list: (request) => listInbox(routes, request)
14031
+ list: (request) => listInbox(api, request)
13874
14032
  },
13875
14033
  messages: {
13876
14034
  read: (request) => withState(async () => {
13877
- const outcome = await readHistory(routes, request, frontier);
14035
+ const outcome = await readHistory(api, request, frontier);
13878
14036
  if (outcome.ok) session.markDirty();
13879
14037
  return outcome;
13880
14038
  }),
@@ -13883,57 +14041,58 @@ function createRaft(options) {
13883
14041
  ...request,
13884
14042
  target: message.target
13885
14043
  }),
13886
- search: (request) => searchMessages(routes, request),
13887
- resolve: (request) => resolveMessage(routes, request),
13888
- react: (request) => reactToMessage(routes, request, "add"),
13889
- unreact: (request) => reactToMessage(routes, request, "remove")
14044
+ search: (request) => searchMessages(api, request),
14045
+ resolve: (request) => resolveMessage(api, request),
14046
+ react: (request) => reactToMessage(api, request, "add"),
14047
+ unreact: (request) => reactToMessage(api, request, "remove")
13890
14048
  },
13891
14049
  attachments: {
13892
- upload: (request) => uploadAttachment(routes, {
14050
+ upload: (request) => uploadAttachment(api, {
13893
14051
  serverUrl,
13894
14052
  fetch: options.fetch ?? fetch,
13895
14053
  headers: Object.fromEntries(new Headers(options.headers)),
13896
14054
  authorization
13897
14055
  }, request),
13898
- download: (request) => downloadAttachment(routes, request),
13899
- comments: (request) => attachmentComments(routes, request)
14056
+ download: (request) => downloadAttachment(api, request),
14057
+ comments: (request) => attachmentComments(api, request)
13900
14058
  },
13901
14059
  mentions: {
13902
- pending: (request) => pendingMentionActions(routes, request),
13903
- execute: (request) => executeMentionAction(routes, request),
13904
- deliveries: (request) => senderMentionDeliveries(routes, request)
14060
+ pending: (request) => pendingMentionActions(api, request),
14061
+ execute: (request) => executeMentionAction(api, request),
14062
+ deliveries: (request) => senderMentionDeliveries(api, request)
13905
14063
  },
14064
+ actions: { prepare: (request) => prepareActionCard(api, request) },
13906
14065
  manual: {
13907
- get: (request) => getManualTopic(routes, request),
13908
- search: (request) => searchManual(routes, request)
14066
+ get: (request) => getManualTopic(api, request),
14067
+ search: (request) => searchManual(api, request)
13909
14068
  },
13910
14069
  tasks: {
13911
- claim: (request) => claimTasks(routes, request),
13912
- list: (request) => listTasks(routes, request),
13913
- create: (request) => createTasks(routes, request),
13914
- unclaim: (request) => unclaimTask(routes, request),
13915
- assign: (request) => assignTask(routes, request),
13916
- updateStatus: (request) => updateTaskStatus(routes, request),
13917
- amend: (request) => amendTask(routes, request),
13918
- history: (request) => taskHistory(routes, request),
13919
- convert: (request) => convertMessageToTask(routes, request),
13920
- delete: (request) => deleteTask(routes, request)
14070
+ claim: (request) => claimTasks(api, request),
14071
+ list: (request) => listTasks(api, request),
14072
+ create: (request) => createTasks(api, request),
14073
+ unclaim: (request) => unclaimTask(api, request),
14074
+ assign: (request) => assignTask(api, request),
14075
+ updateStatus: (request) => updateTaskStatus(api, request),
14076
+ amend: (request) => amendTask(api, request),
14077
+ history: (request) => taskHistory(api, request),
14078
+ convert: (request) => convertMessageToTask(api, request),
14079
+ delete: (request) => deleteTask(api, request)
13921
14080
  },
13922
14081
  channels: {
13923
- join: (request) => joinChannel(routes, request),
13924
- leave: (request) => leaveChannel(routes, request),
13925
- mute: (request) => muteChannel(routes, request),
13926
- unmute: (request) => unmuteChannel(routes, request),
13927
- members: (request) => channelMembers(routes, request)
14082
+ join: (request) => joinChannel(api, request),
14083
+ leave: (request) => leaveChannel(api, request),
14084
+ mute: (request) => muteChannel(api, request),
14085
+ unmute: (request) => unmuteChannel(api, request),
14086
+ members: (request) => channelMembers(api, request)
13928
14087
  },
13929
14088
  threads: {
13930
- list: () => listThreads(routes),
13931
- unfollow: (request) => unfollowThread(routes, request)
14089
+ list: () => listThreads(api),
14090
+ unfollow: (request) => unfollowThread(api, request)
13932
14091
  },
13933
- server: { info: (request) => serverInfo(routes, request) },
14092
+ server: { info: (request) => serverInfo(api, request) },
13934
14093
  profile: {
13935
- show: (request) => showProfile(routes, request),
13936
- update: (request) => updateProfile(routes, request)
14094
+ show: (request) => showProfile(api, request),
14095
+ update: (request) => updateProfile(api, request)
13937
14096
  },
13938
14097
  frontier,
13939
14098
  state: {
package/dist/index.d.ts CHANGED
@@ -980,6 +980,14 @@ export declare function parseRaftState(value: unknown): RaftState | null;
980
980
  /** Hex SHA-256 identifying one logical message (WebCrypto; Workers-safe). */
981
981
  export declare function hashRaftSendContent(target: string, content: string, attachmentIds?: readonly string[]): Promise<string>;
982
982
  //#endregion
983
+ //#region ../shared/src/agentOps/actions.d.ts
984
+ type PrepareActionCardRequest = AgentApiActionPrepareBody;
985
+ interface RaftPreparedCard {
986
+ target: string;
987
+ /** The card message; a human commits it by clicking its action verb. */
988
+ messageId: string;
989
+ }
990
+ //#endregion
983
991
  //#region ../shared/src/agentApiRawClient.d.ts
984
992
  type AgentApiRawClientErrorReason = "missing_route" | "missing_path_param" | "request_contract_mismatch" | "transport_error" | "http_error" | "empty_response" | "response_contract_mismatch";
985
993
  interface AgentApiRawTransportRequest<K extends AgentApiRouteKey = AgentApiRouteKey> {
@@ -988,25 +996,6 @@ interface AgentApiRawTransportRequest<K extends AgentApiRouteKey = AgentApiRoute
988
996
  path: string;
989
997
  body?: unknown;
990
998
  }
991
- type AgentApiRawSuccess<K extends AgentApiRouteKey> = {
992
- ok: true;
993
- routeKey: K;
994
- status: number;
995
- data: AgentApiResponseByRoute[K];
996
- };
997
- type AgentApiRawFailure<K extends AgentApiRouteKey = AgentApiRouteKey> = {
998
- ok: false;
999
- routeKey?: K;
1000
- status?: number;
1001
- reason: AgentApiRawClientErrorReason;
1002
- message: string;
1003
- errorCode?: string | null;
1004
- suggestedNextAction?: string | null;
1005
- proxy?: unknown;
1006
- cause?: unknown;
1007
- response?: unknown;
1008
- };
1009
- type AgentApiRawResult<K extends AgentApiRouteKey> = AgentApiRawSuccess<K> | AgentApiRawFailure<K>;
1010
999
  type AgentApiRawClientResource = AgentApiContract[AgentApiRouteKey]["client"]["resource"];
1011
1000
  type AgentApiRawClientResourceMethod<R extends AgentApiRawClientResource> = { [K in AgentApiRouteKey]: AgentApiContract[K]["client"] extends {
1012
1001
  resource: R;
@@ -1016,9 +1005,6 @@ type AgentApiRouteKeyForClient<R extends AgentApiRawClientResource, M extends Ag
1016
1005
  resource: R;
1017
1006
  method: M;
1018
1007
  } ? K : never; }[AgentApiRouteKey];
1019
- type AgentApiRawClientMethodArgs<K extends AgentApiRouteKey> = [AgentApiRequestParamsByRoute[K]] extends [never] ? [AgentApiRequestQueryByRoute[K]] extends [never] ? [AgentApiRequestBodyByRoute[K]] extends [never] ? [] : [body: AgentApiRequestBodyByRoute[K]] : [query: AgentApiRequestQueryByRoute[K]] : [AgentApiRequestQueryByRoute[K]] extends [never] ? [AgentApiRequestBodyByRoute[K]] extends [never] ? [params: AgentApiRequestParamsByRoute[K]] : [params: AgentApiRequestParamsByRoute[K], body: AgentApiRequestBodyByRoute[K]] : [AgentApiRequestBodyByRoute[K]] extends [never] ? [params: AgentApiRequestParamsByRoute[K], query: AgentApiRequestQueryByRoute[K]] : [params: AgentApiRequestParamsByRoute[K], query: AgentApiRequestQueryByRoute[K], body: AgentApiRequestBodyByRoute[K]];
1020
- type AgentApiRawClientMethod<K extends AgentApiRouteKey> = (...args: AgentApiRawClientMethodArgs<K>) => Promise<AgentApiRawResult<K>>;
1021
- type AgentApiRawClient = { [R in AgentApiRawClientResource]: { [M in AgentApiRawClientResourceMethod<R>]: AgentApiRawClientMethod<AgentApiRouteKeyForClient<R, M>>; }; };
1022
1008
  //#endregion
1023
1009
  //#region ../shared/src/index.d.ts
1024
1010
  /**
@@ -8944,16 +8930,6 @@ interface AgentApiClientFailure<K extends AgentApiRouteKey = AgentApiRouteKey> {
8944
8930
  error: AgentApiClientError;
8945
8931
  }
8946
8932
  type AgentApiClientResult<K extends AgentApiRouteKey> = AgentApiClientSuccess<K> | AgentApiClientFailure<K>;
8947
- type AgentApiClientMethod<K extends AgentApiRouteKey> = (...args: Parameters<AgentApiRawClientMethod<K>>) => Promise<AgentApiClientResult<K>>;
8948
- type AgentApiClientMethods = { [R in keyof AgentApiRawClient]: { [M in keyof AgentApiRawClient[R]]: AgentApiRawClient[R][M] extends AgentApiRawClientMethod<infer K> ? AgentApiClientMethod<K> : never; }; };
8949
- interface AgentApiClientRequestOptions<K extends AgentApiRouteKey> {
8950
- params?: AgentApiRequestParamsByRoute[K];
8951
- query?: AgentApiRequestQueryByRoute[K];
8952
- body?: AgentApiRequestBodyByRoute[K];
8953
- }
8954
- type AgentApiClient = AgentApiClientMethods & {
8955
- request<K extends AgentApiRouteKey>(routeKey: K, options?: AgentApiClientRequestOptions<K>): Promise<AgentApiClientResult<K>>;
8956
- };
8957
8933
  type AgentApiAuthHeaders = Record<string, string>;
8958
8934
  type AgentApiAuthStrategy = AgentApiAuthHeaders | ((request: AgentApiRawTransportRequest) => AgentApiAuthHeaders | Promise<AgentApiAuthHeaders>);
8959
8935
  interface AgentApiFetchTransportOptions {
@@ -9068,12 +9044,24 @@ interface RaftRouteInfo extends RaftRouteMeta {
9068
9044
  annotations: RaftRouteAnnotations;
9069
9045
  }
9070
9046
  /**
9071
- * `routes.<resource>.<method>(params?, query?, body?)` for every route in the
9047
+ * `routes.<resource>.<method>({ params, query, body })` (one named object, typed per route) for every route in the
9072
9048
  * contract, plus route introspection. Reads retry (bounded); writes and
9073
9049
  * destructive reads make exactly one attempt regardless of client retry
9074
9050
  * settings, because the contract says repeating them is not safe.
9075
9051
  */
9076
- type RaftRoutes = AgentApiClient & {
9052
+ type RoutePart<Name extends string, T> = [T] extends [never] ? { [P in Name]?: never; } : {} extends T ? { [P in Name]?: T; } : { [P in Name]: T; };
9053
+ /**
9054
+ * The single input every route takes: `{ params, query, body }`, typed per
9055
+ * route. A part the route does not have is `never` (passing it is a compile
9056
+ * error); a part with required fields is a required property.
9057
+ */
9058
+ type RaftRouteInput<K extends RaftRouteKey> = RoutePart<"params", AgentApiRequestParamsByRoute[K]> & RoutePart<"query", AgentApiRequestQueryByRoute[K]> & RoutePart<"body", AgentApiRequestBodyByRoute[K]>;
9059
+ type RaftRouteMethod<K extends RaftRouteKey> = {} extends RaftRouteInput<K> ? (input?: RaftRouteInput<K>) => Promise<RaftRouteResult<K>> : (input: RaftRouteInput<K>) => Promise<RaftRouteResult<K>>;
9060
+ /** `routes.<resource>.<method>({ params, query, body })` for every route in the contract. */
9061
+ type RaftRouteMethods = { [R in AgentApiRawClientResource]: { [M in AgentApiRawClientResourceMethod<R>]: RaftRouteMethod<AgentApiRouteKeyForClient<R, M>>; }; };
9062
+ type RaftRoutes = RaftRouteMethods & {
9063
+ /** The same call by route key: `request("actionPrepare", { body })`. */
9064
+ request<K extends RaftRouteKey>(routeKey: K, input?: RaftRouteInput<K>): Promise<RaftRouteResult<K>>;
9077
9065
  /** Content hash of the route manifest this SDK was built against. */
9078
9066
  manifestVersion: string;
9079
9067
  /** Static description of one route: capability, side effect, idempotency, audience, retry policy. */
@@ -9094,12 +9082,7 @@ interface CreateRaftRoutesOptions {
9094
9082
  beforeRequest?: infer F;
9095
9083
  } | undefined ? F : never;
9096
9084
  }
9097
- /**
9098
- * Build the routes layer over two transports: a retrying one for routes the
9099
- * contract marks retry-safe, and a single-attempt one for everything else.
9100
- * The split is decided per route from `AGENT_API_ROUTE_META`, never by the
9101
- * caller and never by the HTTP method.
9102
- */
9085
+ /** Build the public routes layer directly (convenience over createRaftRouteClient + raftRoutesFromClient). */
9103
9086
  export declare function createRaftRoutes(options: CreateRaftRoutesOptions): RaftRoutes;
9104
9087
  //#endregion
9105
9088
  //#region src/context.d.ts
@@ -9209,7 +9192,7 @@ type RaftClient = AgentApiMessageClient & RaftManageClient & {
9209
9192
  join(request: RaftChannelJoinRequest): Promise<RaftChannelJoinResult>;
9210
9193
  };
9211
9194
  /**
9212
- * Every Agent API route as `routes.<resource>.<method>(params?, query?, body?)`,
9195
+ * Every Agent API route as `routes.<resource>.<method>({ params, query, body })` (one named object, typed per route),
9213
9196
  * typed from the shared contract, with per-route metadata via `routes.describe`.
9214
9197
  * Retry policy follows the contract: reads may retry, writes make one attempt.
9215
9198
  */
@@ -9451,6 +9434,14 @@ interface Raft {
9451
9434
  messageId: string;
9452
9435
  }): Promise<RaftOutcome<AgentApiSenderMentionDeliveriesResponse, "deliveries" | "empty">>;
9453
9436
  };
9437
+ actions: {
9438
+ /**
9439
+ * Post an action card for a human to confirm (channel:create,
9440
+ * channel:add_member, agent:create, and the integration card types). The
9441
+ * human who clicks it executes it as themselves.
9442
+ */
9443
+ prepare(request: PrepareActionCardRequest): Promise<RaftOutcome<RaftPreparedCard, "prepared">>;
9444
+ };
9454
9445
  manual: {
9455
9446
  /** Fetch a Manual topic; `intent` and `reason` are required and must never carry prompts, credentials, or message payloads. */
9456
9447
  get(request: {
@@ -9543,4 +9534,4 @@ interface Raft {
9543
9534
  }
9544
9535
  export declare function createRaft(options: CreateRaftOptions): Raft;
9545
9536
  //#endregion
9546
- export type { AmendTaskOutcome, AmendTaskRequest, AssignTaskRequest, BootstrapRaftCredentialOptions, CheckInboxOutcome, CheckInboxRequest, ClaimTasksOutcome, ClaimTasksRequest, CreateRaftClientFromStoreOptions, CreateRaftClientOptions, CreateRaftOptions, CreateRaftRoutesOptions, CreateTasksRequest, DrainInboxRequest, ExecuteMentionActionRequest, ListInboxRequest, ListTasksRequest, ManualContext, Raft, RaftAckMode, RaftActionPrepareRequest, RaftActionPrepared, RaftApiError, RaftApiResult, RaftAppConfig, RaftAppConfigPatch, RaftAttachmentBytes, RaftAttachmentUploaded, RaftAvatarUpload, RaftChannelJoinClient, RaftChannelJoinClientResult, RaftChannelJoinError, RaftChannelJoinFailure, RaftChannelJoinOperation, RaftChannelJoinRequest, RaftChannelJoinResult, RaftChannelJoinSuccess, RaftChannelJoinTransportError, RaftChannelMuteState, RaftChannelRef, RaftClaimHeld, RaftClaimHeldLike, RaftClaimResult, RaftClaimRow, RaftClaimRowState, RaftClient, RaftClientError, RaftClientFailure, RaftClientResult, RaftClientSuccess, RaftClientThrottleOptions, RaftClientTransportRequest, RaftContextAgent, RaftContextData, RaftContextError, RaftContextResult, RaftContextServer, RaftCredentialErrorCode, RaftCredentialIdentity, RaftCredentialStore, RaftEvent, RaftEventAttachment, RaftEventExternalMessage, RaftEventsReceiveData, RaftEventsReceiveError, RaftEventsReceiveRequest, RaftEventsReceiveResult, RaftFailure, RaftHeld, RaftHeldBase, RaftHistoryPage, RaftInboxBatch, RaftInboxCommitResult, RaftInboxConversation, RaftInboxDrainSummary, RaftInboxListing, RaftInboxNotice, RaftManageClient, RaftMessage, RaftMessageAttachment, RaftMessageTask, RaftNextStep, RaftNoticeFlag, RaftNoticeTarget, RaftOpError, RaftOpErrorCode, RaftOutcome, RaftPendingMentions, RaftProfile, RaftProfileUpdate, RaftRouteAnnotations, RaftRouteInfo, RaftRouteKey, RaftRouteMeta, RaftRouteResult, RaftRouteRetryPolicy, RaftRoutes, RaftSdkConfigurationErrorCode, RaftSearchPage, RaftSendContinuation, RaftSenderType, RaftSent, RaftServerInfo, RaftServerProfile, RaftServerUpdate, RaftState, RaftStateContinuation, RaftStateSaveErrorHandler, RaftStateStore, RaftTaskBoard, RaftTaskStatus, RaftTasksCreated, RaftWebhookStatus, ReactRequest, ReadHistoryRequest, SearchMessagesRequest, SeenAttestation, SeenFrontierSnapshot, SendMessageOutcome, SendMessageRequest, ServerInfoRequest, ServerInfoSection, StoredRaftCredential, TaskRef, UpdateTaskStatusOutcome, UpdateTaskStatusRequest, UploadAttachmentRequest, VerifyNoticeInput, VerifyNoticeRejection, VerifyNoticeResult };
9537
+ export type { AmendTaskOutcome, AmendTaskRequest, AssignTaskRequest, BootstrapRaftCredentialOptions, CheckInboxOutcome, CheckInboxRequest, ClaimTasksOutcome, ClaimTasksRequest, CreateRaftClientFromStoreOptions, CreateRaftClientOptions, CreateRaftOptions, CreateRaftRoutesOptions, CreateTasksRequest, DrainInboxRequest, ExecuteMentionActionRequest, ListInboxRequest, ListTasksRequest, ManualContext, PrepareActionCardRequest, Raft, RaftAckMode, RaftActionPrepareRequest, RaftActionPrepared, RaftApiError, RaftApiResult, RaftAppConfig, RaftAppConfigPatch, RaftAttachmentBytes, RaftAttachmentUploaded, RaftAvatarUpload, RaftChannelJoinClient, RaftChannelJoinClientResult, RaftChannelJoinError, RaftChannelJoinFailure, RaftChannelJoinOperation, RaftChannelJoinRequest, RaftChannelJoinResult, RaftChannelJoinSuccess, RaftChannelJoinTransportError, RaftChannelMuteState, RaftChannelRef, RaftClaimHeld, RaftClaimHeldLike, RaftClaimResult, RaftClaimRow, RaftClaimRowState, RaftClient, RaftClientError, RaftClientFailure, RaftClientResult, RaftClientSuccess, RaftClientThrottleOptions, RaftClientTransportRequest, RaftContextAgent, RaftContextData, RaftContextError, RaftContextResult, RaftContextServer, RaftCredentialErrorCode, RaftCredentialIdentity, RaftCredentialStore, RaftEvent, RaftEventAttachment, RaftEventExternalMessage, RaftEventsReceiveData, RaftEventsReceiveError, RaftEventsReceiveRequest, RaftEventsReceiveResult, RaftFailure, RaftHeld, RaftHeldBase, RaftHistoryPage, RaftInboxBatch, RaftInboxCommitResult, RaftInboxConversation, RaftInboxDrainSummary, RaftInboxListing, RaftInboxNotice, RaftManageClient, RaftMessage, RaftMessageAttachment, RaftMessageTask, RaftNextStep, RaftNoticeFlag, RaftNoticeTarget, RaftOpError, RaftOpErrorCode, RaftOutcome, RaftPendingMentions, RaftPreparedCard, RaftProfile, RaftProfileUpdate, RaftRouteAnnotations, RaftRouteInfo, RaftRouteKey, RaftRouteMeta, RaftRouteResult, RaftRouteRetryPolicy, RaftRoutes, RaftSdkConfigurationErrorCode, RaftSearchPage, RaftSendContinuation, RaftSenderType, RaftSent, RaftServerInfo, RaftServerProfile, RaftServerUpdate, RaftState, RaftStateContinuation, RaftStateSaveErrorHandler, RaftStateStore, RaftTaskBoard, RaftTaskStatus, RaftTasksCreated, RaftWebhookStatus, ReactRequest, ReadHistoryRequest, SearchMessagesRequest, SeenAttestation, SeenFrontierSnapshot, SendMessageOutcome, SendMessageRequest, ServerInfoRequest, ServerInfoSection, StoredRaftCredential, TaskRef, UpdateTaskStatusOutcome, UpdateTaskStatusRequest, UploadAttachmentRequest, VerifyNoticeInput, VerifyNoticeRejection, VerifyNoticeResult };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botiverse/raft-sdk",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.6",
4
4
  "license": "FSL-1.1-ALv2",
5
5
  "description": "Typed Raft Agent API client for external agents and bots.",
6
6
  "type": "module",