@alfe.ai/agent-api-client 0.6.0 → 0.7.0

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/index.js CHANGED
@@ -14,6 +14,8 @@ const RETRYABLE_STATUS = new Set([
14
14
  504
15
15
  ]);
16
16
  const RETRY_DELAY_MS = 500;
17
+ const IMAGE_POLL_INTERVAL_MS = 2e3;
18
+ const IMAGE_JOB_TIMEOUT_MS = 18e4;
17
19
  function sleep(ms) {
18
20
  return new Promise((resolve) => {
19
21
  setTimeout(resolve, ms);
@@ -591,36 +593,50 @@ var AgentApiClient = class {
591
593
  }
592
594
  /**
593
595
  * Generate an image from a text prompt and get back a STABLE, public URL
594
- * (served from the agent-assets CDN — it does not expire). The image is
595
- * generated + stored server-side; embed the returned `imageUrl` in a reply as
596
- * markdown to show it to the user.
596
+ * (served from the agent-assets CDN — it does not expire). Embed the returned
597
+ * `imageUrl` in a reply as markdown to show it to the user.
597
598
  *
598
- * Unlike most methods this reads the server's error body so a bad-request
599
- * detail (e.g. an unsupported `size`) reaches the caller instead of an opaque
600
- * "request failed (400)". Not retried generation is expensive and
601
- * non-idempotent.
599
+ * ASYNC: `gpt-image-1` routinely runs 30–60s, which exceeds the API Gateway
600
+ * 30s ceiling, so this enqueues a job (`POST /agent/images/generate`
601
+ * `jobId`) then polls (`GET /agent/images/{jobId}`) until it completes. The
602
+ * worker's real failure message (e.g. an unsupported `size`) surfaces via the
603
+ * job's `error` field.
602
604
  */
603
605
  async generateImage(args) {
604
606
  const headers = new Headers();
605
607
  headers.set("Authorization", `Bearer ${this.apiKey}`);
606
608
  headers.set("Content-Type", "application/json");
607
- const res = await fetch(`${this.apiUrl}/agent/images/generate`, {
609
+ const enqueueRes = await fetch(`${this.apiUrl}/agent/images/generate`, {
608
610
  method: "POST",
609
611
  headers,
610
612
  body: JSON.stringify(args),
611
- signal: AbortSignal.timeout(33e3)
613
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
612
614
  });
613
- if (!res.ok) {
614
- const body = await res.text().catch(() => "");
615
- let detail = "";
615
+ if (!enqueueRes.ok) throw new Error(`Image generation failed to start (${String(enqueueRes.status)})`);
616
+ const { data: enqueued } = await enqueueRes.json();
617
+ const jobId = enqueued.jobId;
618
+ const deadline = Date.now() + IMAGE_JOB_TIMEOUT_MS;
619
+ while (Date.now() < deadline) {
620
+ await sleep(IMAGE_POLL_INTERVAL_MS);
621
+ let job;
616
622
  try {
617
- const parsed = JSON.parse(body);
618
- const msg = typeof parsed.message === "string" ? parsed.message : typeof parsed.error === "string" ? parsed.error : "";
619
- if (msg) detail = `: ${msg}`;
620
- } catch {}
621
- throw new Error(`Image generation failed (${String(res.status)})${detail}`);
623
+ job = await this.request(`/agent/images/${jobId}`);
624
+ } catch {
625
+ continue;
626
+ }
627
+ if (job.status === "completed") {
628
+ if (!job.imageUrl) throw new Error("Image generation completed without a URL");
629
+ return {
630
+ imageUrl: job.imageUrl,
631
+ model: job.model ?? args.model ?? "gpt-image-1"
632
+ };
633
+ }
634
+ if (job.status === "failed") {
635
+ const detail = job.error ? `: ${job.error.split("\n")[0]}` : "";
636
+ throw new Error(`Image generation failed${detail}`);
637
+ }
622
638
  }
623
- return (await res.json()).data;
639
+ throw new Error("Image generation timed out");
624
640
  }
625
641
  async recordActivity(data) {
626
642
  return this.request("/agent/activity", {
@@ -979,30 +995,6 @@ var AgentApiClient = class {
979
995
  async getScopeProfile(scopeType, scopeId) {
980
996
  return this.request(`/agent/org/profile/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}`);
981
997
  }
982
- /** List a scope's facts (non-semantic, full enumeration). */
983
- async listScopeFacts(scopeType, scopeId, opts) {
984
- const qs = new URLSearchParams();
985
- if (opts?.limit !== void 0) qs.set("limit", String(opts.limit));
986
- if (opts?.cursor) qs.set("cursor", opts.cursor);
987
- const query = qs.toString();
988
- return this.request(`/agent/org/facts/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}${query ? `?${query}` : ""}`);
989
- }
990
- /**
991
- * Publish a fact at a scope. This is the deliberate cross-agent learning
992
- * boundary — facts are attributable (the agent is recorded as author) and
993
- * never an automatic merge. Membership is enforced server-side (403 for a
994
- * non-member scope).
995
- */
996
- async createScopeFact(scopeType, scopeId, text) {
997
- return this.request(`/agent/org/facts/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}`, {
998
- method: "POST",
999
- body: JSON.stringify({ text })
1000
- });
1001
- }
1002
- /** Delete a fact at a scope. */
1003
- async deleteScopeFact(scopeType, scopeId, factId) {
1004
- return this.request(`/agent/org/facts/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}/${encodeURIComponent(factId)}`, { method: "DELETE" });
1005
- }
1006
998
  /** List a scope's docs (the org-files corpus; mirrored to shared/<scope>/). */
1007
999
  async listScopeDocs(scopeType, scopeId, opts) {
1008
1000
  const qs = new URLSearchParams();
@@ -1058,6 +1050,55 @@ var AgentApiClient = class {
1058
1050
  }
1059
1051
  return { filePath: presign.filePath };
1060
1052
  }
1053
+ /**
1054
+ * Open a change request against a scope's knowledge resource. For a doc
1055
+ * create/update, `services/org` returns a presigned staging PUT; this method
1056
+ * uploads the proposed `content` to it (echoing the same Content-Type that
1057
+ * was signed), mirroring `writeScopeDoc`. The staged body is applied to the
1058
+ * canonical doc — attributed to this agent — only when a reviewer approves.
1059
+ */
1060
+ async proposeScopeChange(scopeType, scopeId, input) {
1061
+ const isDocBody = input.resourceType === "doc" && input.operation !== "delete";
1062
+ const contentType = input.contentType ?? "text/markdown";
1063
+ const result = await this.request(`/agent/org/change-requests/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}`, {
1064
+ method: "POST",
1065
+ body: JSON.stringify({
1066
+ resourceType: input.resourceType,
1067
+ operation: input.operation,
1068
+ rationale: input.rationale,
1069
+ targetPath: input.targetPath,
1070
+ proposedContentType: isDocBody ? contentType : void 0,
1071
+ proposedValue: input.proposedValue
1072
+ })
1073
+ });
1074
+ if (isDocBody && result.uploadUrl) {
1075
+ const putHeaders = new Headers(result.requiredHeaders ?? {});
1076
+ putHeaders.set("Content-Type", contentType);
1077
+ const res = await fetch(result.uploadUrl, {
1078
+ method: "PUT",
1079
+ body: input.content ?? "",
1080
+ headers: putHeaders,
1081
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
1082
+ });
1083
+ if (!res.ok) {
1084
+ await res.text();
1085
+ throw new Error(`Change-request body upload failed (${String(res.status)})`);
1086
+ }
1087
+ }
1088
+ return result.changeRequest;
1089
+ }
1090
+ /**
1091
+ * List the agent's OWN change requests in a scope (filtered server-side to
1092
+ * this agent as proposer). Pass `status` to narrow to open / approved / etc.
1093
+ */
1094
+ async listScopeChangeRequests(scopeType, scopeId, opts) {
1095
+ const qs = new URLSearchParams();
1096
+ if (opts?.status) qs.set("status", opts.status);
1097
+ if (opts?.limit !== void 0) qs.set("limit", String(opts.limit));
1098
+ if (opts?.cursor) qs.set("cursor", opts.cursor);
1099
+ const query = qs.toString();
1100
+ return this.request(`/agent/org/change-requests/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}${query ? `?${query}` : ""}`);
1101
+ }
1061
1102
  async registerDatabaseCredentials() {
1062
1103
  return this.request("/agent/database/register", { method: "POST" });
1063
1104
  }