@prismer/sdk 2.0.3 → 2.0.5

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/dist/cli.js CHANGED
@@ -1753,6 +1753,44 @@ function resolveBaseUrl(explicit) {
1753
1753
  }
1754
1754
  return void 0;
1755
1755
  }
1756
+ function generateIdempotencyKey() {
1757
+ try {
1758
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1759
+ return crypto.randomUUID();
1760
+ }
1761
+ } catch {
1762
+ }
1763
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
1764
+ const r = Math.random() * 16 | 0;
1765
+ return (c === "x" ? r : r & 3 | 8).toString(16);
1766
+ });
1767
+ }
1768
+ function buildSendPayload(content, options) {
1769
+ const key = options?.idempotencyKey ?? generateIdempotencyKey();
1770
+ const isBlocks = Array.isArray(content);
1771
+ const body = {
1772
+ // For ContentBlock[] inputs, populate `content` as a fallback summary
1773
+ // (server's legacy `content` column stays compatible during the 6-sprint
1774
+ // double-write window from §4.6); the SOT is `contentBlocks`.
1775
+ content: isBlocks ? "" : content,
1776
+ type: options?.type ?? "text",
1777
+ metadata: options?.metadata,
1778
+ attachments: options?.attachments,
1779
+ parentId: options?.parentId,
1780
+ quotedMessageId: options?.quotedMessageId,
1781
+ idempotencyKey: key
1782
+ };
1783
+ if (isBlocks) {
1784
+ body.contentBlocks = content;
1785
+ } else if (options?.contentBlocks) {
1786
+ body.contentBlocks = options.contentBlocks;
1787
+ }
1788
+ return {
1789
+ body,
1790
+ opts: { headers: { "X-Idempotency-Key": key } },
1791
+ key
1792
+ };
1793
+ }
1756
1794
  var AccountClient = class {
1757
1795
  constructor(_r) {
1758
1796
  this._r = _r;
@@ -1795,16 +1833,17 @@ var DirectClient = class {
1795
1833
  constructor(_r) {
1796
1834
  this._r = _r;
1797
1835
  }
1798
- /** Send a direct message to a user */
1836
+ /**
1837
+ * Send a direct message to a user.
1838
+ *
1839
+ * v2.0 §4.6 — `content` may now be a `ContentBlock[]` for multimodal sends.
1840
+ * v2.0 §3.0.2 Gap A-④ — when `options.idempotencyKey` is omitted, the SDK
1841
+ * generates a UUID per call and stamps it into the `X-Idempotency-Key`
1842
+ * header. Pass the same key across retries to trigger server dedup.
1843
+ */
1799
1844
  async send(userId, content, options) {
1800
- return this._r("POST", `/api/im/direct/${userId}/messages`, {
1801
- content,
1802
- type: options?.type ?? "text",
1803
- metadata: options?.metadata,
1804
- attachments: options?.attachments,
1805
- parentId: options?.parentId,
1806
- quotedMessageId: options?.quotedMessageId
1807
- });
1845
+ const { body, opts } = buildSendPayload(content, options);
1846
+ return this._r("POST", `/api/im/direct/${userId}/messages`, body, void 0, opts);
1808
1847
  }
1809
1848
  /** Get direct message history with a user */
1810
1849
  async getMessages(userId, options) {
@@ -1830,16 +1869,15 @@ var GroupsClient = class {
1830
1869
  async get(groupId) {
1831
1870
  return this._r("GET", `/api/im/groups/${groupId}`);
1832
1871
  }
1833
- /** Send a message to a group */
1872
+ /**
1873
+ * Send a message to a group.
1874
+ *
1875
+ * v2.0 §4.6 — `content` may now be a `ContentBlock[]` for multimodal sends.
1876
+ * v2.0 §3.0.2 Gap A-④ — auto-generates `X-Idempotency-Key` per call.
1877
+ */
1834
1878
  async send(groupId, content, options) {
1835
- return this._r("POST", `/api/im/groups/${groupId}/messages`, {
1836
- content,
1837
- type: options?.type ?? "text",
1838
- metadata: options?.metadata,
1839
- attachments: options?.attachments,
1840
- parentId: options?.parentId,
1841
- quotedMessageId: options?.quotedMessageId
1842
- });
1879
+ const { body, opts } = buildSendPayload(content, options);
1880
+ return this._r("POST", `/api/im/groups/${groupId}/messages`, body, void 0, opts);
1843
1881
  }
1844
1882
  /** Get group message history */
1845
1883
  async getMessages(groupId, options) {
@@ -1909,16 +1947,23 @@ var MessagesClient = class {
1909
1947
  constructor(_r) {
1910
1948
  this._r = _r;
1911
1949
  }
1912
- /** Send a message to a conversation */
1950
+ /**
1951
+ * Send a message to a conversation.
1952
+ *
1953
+ * v2.0 §4.6 — `content` may now be a `ContentBlock[]` for multimodal sends.
1954
+ * The SDK serialises ContentBlock[] into `body.contentBlocks` (preferred
1955
+ * path) while still writing a string `content` for legacy renderers during
1956
+ * the §4.6 6-sprint double-read window.
1957
+ *
1958
+ * v2.0 §3.0.2 Gap A-④ — when `options.idempotencyKey` is omitted, the SDK
1959
+ * auto-generates `crypto.randomUUID()` per call and includes it as the
1960
+ * `X-Idempotency-Key` HTTP header. The server applies UNIQUE
1961
+ * `(conversationId, idempotencyKey)` dedup; pass the same key across
1962
+ * retries to safely re-send.
1963
+ */
1913
1964
  async send(conversationId, content, options) {
1914
- return this._r("POST", `/api/im/messages/${conversationId}`, {
1915
- content,
1916
- type: options?.type ?? "text",
1917
- metadata: options?.metadata,
1918
- attachments: options?.attachments,
1919
- parentId: options?.parentId,
1920
- quotedMessageId: options?.quotedMessageId
1921
- });
1965
+ const { body, opts } = buildSendPayload(content, options);
1966
+ return this._r("POST", `/api/im/messages/${conversationId}`, body, void 0, opts);
1922
1967
  }
1923
1968
  /** Get message history for a conversation */
1924
1969
  async getHistory(conversationId, options) {
@@ -2456,6 +2501,46 @@ var EvolutionSkillsClient = class {
2456
2501
  return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
2457
2502
  }
2458
2503
  };
2504
+ var AgentsClient = class {
2505
+ constructor(_r) {
2506
+ this._r = _r;
2507
+ }
2508
+ async spec(agentId, workspaceId) {
2509
+ const query = workspaceId ? { workspaceId } : void 0;
2510
+ return this._r("GET", `/api/im/agents/${encodeURIComponent(agentId)}/spec`, void 0, query);
2511
+ }
2512
+ async snapshot(agentId, options) {
2513
+ return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/snapshot`, options ?? {});
2514
+ }
2515
+ async snapshots(agentId, options) {
2516
+ const query = {};
2517
+ if (options?.cursor) query.cursor = options.cursor;
2518
+ if (options?.limit != null) query.limit = String(options.limit);
2519
+ return this._r("GET", `/api/im/agents/${encodeURIComponent(agentId)}/snapshots`, void 0, query);
2520
+ }
2521
+ async restore(agentId, options) {
2522
+ return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/restore`, options);
2523
+ }
2524
+ async publish(agentId, options) {
2525
+ return this._r("POST", `/api/im/agents/${encodeURIComponent(agentId)}/publish`, options ?? {});
2526
+ }
2527
+ async listPacks(options) {
2528
+ const query = {};
2529
+ if (options?.q) query.q = options.q;
2530
+ if (options?.curatedQuality) query.curatedQuality = options.curatedQuality;
2531
+ if (options?.license) query.license = options.license;
2532
+ if (options?.publisherDid) query.publisherDid = options.publisherDid;
2533
+ if (options?.cursor) query.cursor = options.cursor;
2534
+ if (options?.limit != null) query.limit = String(options.limit);
2535
+ return this._r("GET", "/api/im/agent-packs", void 0, query);
2536
+ }
2537
+ async forkPack(packIdOrSlug, options) {
2538
+ return this._r("POST", `/api/im/agent-packs/${encodeURIComponent(packIdOrSlug)}/fork`, options);
2539
+ }
2540
+ async deletePack(packIdOrSlug) {
2541
+ return this._r("DELETE", `/api/im/agent-packs/${encodeURIComponent(packIdOrSlug)}`);
2542
+ }
2543
+ };
2459
2544
  var EvolutionClient = class {
2460
2545
  constructor(_r) {
2461
2546
  this._r = _r;
@@ -3696,6 +3781,7 @@ var IMClient = class {
3696
3781
  this.knowledge = new KnowledgeLinkClient(request2);
3697
3782
  this.identity = new IdentityClient(request2);
3698
3783
  this.security = new SecurityClient(request2);
3784
+ this.agents = new AgentsClient(request2);
3699
3785
  this.evolution = new EvolutionClient(request2);
3700
3786
  this.community = new CommunityHub(request2, communityHubConfig ?? void 0);
3701
3787
  this.files = new FilesClient(request2, wsBase, fetchFn, getAuthHeaders);
@@ -3761,6 +3847,9 @@ var PrismerClient = class {
3761
3847
  if (config.offline) {
3762
3848
  this._offlineManager = new OfflineManager(
3763
3849
  config.offline.storage,
3850
+ // OfflineManager has a 4-arg RequestFn signature (legacy); the 5th
3851
+ // opts param is dropped on the offline path until OfflineManager is
3852
+ // upgraded. Online path forwards opts (see below).
3764
3853
  (m, p, b, q) => this._request(m, p, b, q),
3765
3854
  config.offline
3766
3855
  );
@@ -3768,23 +3857,28 @@ var PrismerClient = class {
3768
3857
  (err) => console.warn("[PrismerSDK] Offline storage init failed:", err)
3769
3858
  );
3770
3859
  }
3771
- let imRequest = this._offlineManager ? (m, p, b, q) => this._offlineManager.dispatch(m, p, b, q) : (m, p, b, q) => this._request(m, p, b, q);
3860
+ let imRequest = this._offlineManager ? (m, p, b, q, _opts) => (
3861
+ // Offline path: opts dropped (manager doesn't forward headers yet).
3862
+ // Idempotency key is also stamped into the body JSON, so server-side
3863
+ // dedup still works via the JSON field even without the header.
3864
+ this._offlineManager.dispatch(m, p, b, q)
3865
+ ) : (m, p, b, q, opts) => this._request(m, p, b, q, opts);
3772
3866
  if (config.identity) {
3773
3867
  const baseRequest = imRequest;
3774
- imRequest = (method, path5, body, query) => {
3868
+ imRequest = (method, path5, body, query, opts) => {
3775
3869
  if (method === "POST" && path5.includes("/messages") && body) {
3776
3870
  const b = body;
3777
3871
  if (!b.signature && !b.skipSigning) {
3778
3872
  const ready = this._identityReady || Promise.resolve();
3779
3873
  return ready.then(() => {
3780
3874
  if (this._identity) {
3781
- return this._signAndSend(baseRequest, method, path5, b, query);
3875
+ return this._signAndSend(baseRequest, method, path5, b, query, opts);
3782
3876
  }
3783
- return baseRequest(method, path5, body, query);
3877
+ return baseRequest(method, path5, body, query, opts);
3784
3878
  });
3785
3879
  }
3786
3880
  }
3787
- return baseRequest(method, path5, body, query);
3881
+ return baseRequest(method, path5, body, query, opts);
3788
3882
  };
3789
3883
  }
3790
3884
  this.im = new IMClient(
@@ -3806,9 +3900,9 @@ var PrismerClient = class {
3806
3900
  return this._identity;
3807
3901
  }
3808
3902
  /** Auto-sign a message body and send (v1.8.0 S1) */
3809
- async _signAndSend(baseRequest, method, path5, body, query) {
3903
+ async _signAndSend(baseRequest, method, path5, body, query, opts) {
3810
3904
  if (this._identityReady) await this._identityReady;
3811
- if (!this._identity) return baseRequest(method, path5, body, query);
3905
+ if (!this._identity) return baseRequest(method, path5, body, query, opts);
3812
3906
  const content = body.content || "";
3813
3907
  const contentHashBytes = new Uint8Array(
3814
3908
  await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content))
@@ -3825,7 +3919,7 @@ var PrismerClient = class {
3825
3919
  contentHash,
3826
3920
  signature,
3827
3921
  signedAt: timestamp
3828
- }, query);
3922
+ }, query, opts);
3829
3923
  }
3830
3924
  /** Build auth headers for raw HTTP requests (used by file upload) */
3831
3925
  _getAuthHeaders() {
@@ -3883,7 +3977,7 @@ var PrismerClient = class {
3883
3977
  // --------------------------------------------------------------------------
3884
3978
  // Internal request helper
3885
3979
  // --------------------------------------------------------------------------
3886
- async _request(method, path5, body, query, _isRetry) {
3980
+ async _request(method, path5, body, query, opts, _isRetry) {
3887
3981
  const controller = new AbortController();
3888
3982
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3889
3983
  try {
@@ -3898,6 +3992,9 @@ var PrismerClient = class {
3898
3992
  if (this.imAgent) {
3899
3993
  headers["X-IM-Agent"] = this.imAgent;
3900
3994
  }
3995
+ if (opts?.headers) {
3996
+ for (const [k, v] of Object.entries(opts.headers)) headers[k] = v;
3997
+ }
3901
3998
  const init = { method, headers, signal: controller.signal };
3902
3999
  if (body !== void 0) {
3903
4000
  headers["Content-Type"] = "application/json";
@@ -3907,10 +4004,10 @@ var PrismerClient = class {
3907
4004
  const data = await response.json();
3908
4005
  if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !path5.includes("/token/refresh")) {
3909
4006
  try {
3910
- const refreshRes = await this._request("POST", "/api/im/token/refresh", void 0, void 0, true);
4007
+ const refreshRes = await this._request("POST", "/api/im/token/refresh", void 0, void 0, void 0, true);
3911
4008
  if (refreshRes?.ok && refreshRes?.data?.token) {
3912
4009
  this.apiKey = refreshRes.data.token;
3913
- return this._request(method, path5, body, query, true);
4010
+ return this._request(method, path5, body, query, opts, true);
3914
4011
  }
3915
4012
  } catch {
3916
4013
  }
@@ -7777,6 +7874,111 @@ function parseIntOpt2(value) {
7777
7874
  return n;
7778
7875
  }
7779
7876
 
7877
+ // src/commands/agent.ts
7878
+ function register13(parent, getIMClient2, _getAPIClient) {
7879
+ const agent = parent.command("agent").description("Manage agent specs, snapshots, publish, and fork");
7880
+ agent.command("spec <agent-id>").description("Read an AgentSpec 4-tuple").option("--workspace-id <id>", "Workspace scope").option("--json", "Output raw JSON response").action(async (agentId, opts) => {
7881
+ const res = await getIMClient2().im.agents.spec(agentId, opts.workspaceId);
7882
+ printOrExit(res, opts.json, (data) => {
7883
+ console.log(`AgentSpec ${agentId}`);
7884
+ console.log(` identity: ${data.identity.displayName} (${data.identity.imUserId})`);
7885
+ console.log(` role: ${data.definition.roleTemplateSlug ?? "custom"}`);
7886
+ console.log(` skills: ${data.skills.length}`);
7887
+ console.log(` memory: ${data.memory.scope}`);
7888
+ });
7889
+ });
7890
+ agent.command("snapshot <agent-id>").description("Create an agent-level snapshot").option("--include-memory", "Include memory dump reference").option("--label <label>", "Snapshot label").option("--json", "Output raw JSON response").action(async (agentId, opts) => {
7891
+ const res = await getIMClient2().im.agents.snapshot(agentId, {
7892
+ includeMemory: Boolean(opts.includeMemory),
7893
+ label: opts.label
7894
+ });
7895
+ printOrExit(res, opts.json, (data) => {
7896
+ console.log(`Snapshot created: ${data.id}`);
7897
+ console.log(` agent: ${data.agentImUserId}`);
7898
+ console.log(` memory: ${data.includeMemory ? "included" : "stripped"}`);
7899
+ console.log(` size: ${data.sizeBytes ?? 0} bytes`);
7900
+ });
7901
+ });
7902
+ agent.command("snapshots <agent-id>").description("List agent snapshots").option("-n, --limit <n>", "Max snapshots", "20").option("--json", "Output raw JSON response").action(async (agentId, opts) => {
7903
+ const res = await getIMClient2().im.agents.snapshots(agentId, { limit: parseInt(opts.limit, 10) || 20 });
7904
+ printOrExit(res, opts.json, (data) => {
7905
+ const rows = data.items ?? [];
7906
+ if (rows.length === 0) {
7907
+ console.log("No snapshots.");
7908
+ return;
7909
+ }
7910
+ for (const item of rows) {
7911
+ console.log(`${item.id} ${item.createdAt} memory=${item.includeMemory ? "yes" : "no"} size=${item.sizeBytes ?? 0}`);
7912
+ }
7913
+ });
7914
+ });
7915
+ agent.command("restore <agent-id> <snapshot-id>").description("Restore agent definition and skills from a snapshot").option("--override-memory", "Restore memory reference when available").option("--json", "Output raw JSON response").action(async (agentId, snapshotId, opts) => {
7916
+ const res = await getIMClient2().im.agents.restore(agentId, {
7917
+ snapshotId,
7918
+ overrideMemory: Boolean(opts.overrideMemory)
7919
+ });
7920
+ printOrExit(res, opts.json, () => {
7921
+ console.log(`Restored ${agentId} from ${snapshotId}`);
7922
+ });
7923
+ });
7924
+ agent.command("publish <agent-id>").description("Publish an agent as an Agent Pack").requiredOption("--slug <slug>", "Agent Pack slug").option("--version <version>", "Package version", "1.0.0").option("--license <license>", "Package license", "proprietary").option("--title <title>", "Display title metadata").option("--description <description>", "Description metadata").option("--json", "Output raw JSON response").action(async (agentId, opts) => {
7925
+ const res = await getIMClient2().im.agents.publish(agentId, {
7926
+ slug: opts.slug,
7927
+ version: opts.version,
7928
+ license: opts.license,
7929
+ metadata: {
7930
+ ...opts.title ? { title: opts.title } : {},
7931
+ ...opts.description ? { description: opts.description } : {}
7932
+ },
7933
+ stripMemory: true
7934
+ });
7935
+ printOrExit(res, opts.json, (data) => {
7936
+ console.log(`Agent Pack published: ${data.slug}@${data.version}`);
7937
+ console.log(` id: ${data.id}`);
7938
+ console.log(` publisher: ${data.publisherDid}`);
7939
+ });
7940
+ });
7941
+ agent.command("packs").description("List published Agent Packs").option("-q, --query <query>", "Search query").option("-n, --limit <n>", "Max packs", "20").option("--json", "Output raw JSON response").action(async (opts) => {
7942
+ const res = await getIMClient2().im.agents.listPacks({
7943
+ q: opts.query,
7944
+ limit: parseInt(opts.limit, 10) || 20
7945
+ });
7946
+ printOrExit(res, opts.json, (data) => {
7947
+ const rows = data.items ?? [];
7948
+ if (rows.length === 0) {
7949
+ console.log("No Agent Packs.");
7950
+ return;
7951
+ }
7952
+ for (const item of rows) {
7953
+ console.log(`${item.slug}@${item.version} ${item.id} ${item.license} ${item.publisherDid}`);
7954
+ }
7955
+ });
7956
+ });
7957
+ agent.command("fork <pack-id-or-slug>").description("Fork an Agent Pack into a workspace").requiredOption("--workspace-id <id>", "Target workspace id").option("--display-name <name>", "New agent display name").option("--json", "Output raw JSON response").action(async (packId, opts) => {
7958
+ const res = await getIMClient2().im.agents.forkPack(packId, {
7959
+ targetWorkspaceId: opts.workspaceId,
7960
+ displayName: opts.displayName
7961
+ });
7962
+ printOrExit(res, opts.json, (data) => {
7963
+ console.log(`Forked Agent Pack ${packId}`);
7964
+ console.log(` new agent: ${data.newImUserId}`);
7965
+ console.log(` did: ${data.newDid ?? "not issued"}`);
7966
+ });
7967
+ });
7968
+ }
7969
+ function printOrExit(res, json, print) {
7970
+ if (!res.ok || !res.data) {
7971
+ process.stderr.write(`Error: ${res.error?.message || "request failed"}
7972
+ `);
7973
+ process.exit(1);
7974
+ }
7975
+ if (json) {
7976
+ process.stdout.write(JSON.stringify(res.data, null, 2) + "\n");
7977
+ return;
7978
+ }
7979
+ print(res.data);
7980
+ }
7981
+
7780
7982
  // src/daemon.ts
7781
7983
  var fs3 = __toESM(require("fs"));
7782
7984
  var path3 = __toESM(require("path"));
@@ -8701,6 +8903,7 @@ register9(program, getIMClient, getAPIClient);
8701
8903
  register10(program, getIMClient, getAPIClient);
8702
8904
  register11(program, getIMClient, getAPIClient);
8703
8905
  register12(program, getIMClient, getAPIClient);
8906
+ register13(program, getIMClient, getAPIClient);
8704
8907
  program.command("send").description("Send a direct message (shortcut for: im send)").argument("<user-id-or-username>", "Target user/agent IM user ID (or username with --by-username)").argument("<message>", "Message content").option("-t, --type <type>", "Message type: text, markdown, code, etc.", "text").option("--reply-to <id>", "Reply to a message ID").option("--conversation-id <id>", "Pin message to a specific conversation/session").option("--asset-id <id>", "Attach a previously uploaded asset (treats type as file)").option("--by-username", "Treat the first argument as a username; resolve to imUserId first").option("--json", "JSON output").action(async (target, message, opts) => {
8705
8908
  const client = getIMClient();
8706
8909
  let userId = target;