@ambasada/village 0.8.5 → 0.8.7

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/client.d.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  import { Ambasada, type AmbasadaOptions } from "@ambasada/sdk/platform";
2
2
  import { type IncomingRequest } from "./receipt.js";
3
3
  import type { AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutDelivery, CheckoutDeliveryInput, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, ChatLine, ChatProposal, ChatTurn, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
4
+ /** How long a utility call may take here: the API's own limit plus a margin, so the SDK never gives up on a call the API finishes and charges. */
5
+ export declare const CLIENT_TIMEOUT_MS: {
6
+ readonly llm: number;
7
+ readonly image: number;
8
+ readonly tool: number;
9
+ };
4
10
  /** The Hack A Ton API. The platform API (`@ambasada/sdk`) is https://api.ambasada.dev. */
5
11
  export declare const DEFAULT_VILLAGE_API_URL = "https://hackaton.ambasada.id";
6
12
  export declare const DEFAULT_MODEL = "deepseek-v4.1-flash";
7
13
  export interface VillageOptions extends AmbasadaOptions {
8
- /** Team key (`sk_live_…`, from the village app → Developers). Default: `AMBASADA_API_KEY`. */
14
+ /** Team key (`sk_live_…`, from the village app → Team → Settings). Default: `AMBASADA_API_KEY`. */
9
15
  apiKey?: string;
10
16
  /**
11
17
  * Default: `AMBASADA_VILLAGE_API_URL`, else `AMBASADA_API_URL` when it is not the platform host (0.7
@@ -74,7 +80,9 @@ export declare class Village extends Ambasada {
74
80
  }>;
75
81
  /**
76
82
  * Tells the village what a paid checkout delivered (`text` ≤ 4000 chars, an https `url`, or both): the person sees it
77
- * with the purchase in their app and can rate it. Again for the same session replaces it. CONFLICT before it is paid.
83
+ * with the purchase in their app (one push, the first time) and can rate it. The same delivery again changes nothing;
84
+ * a new text replaces it. `{ status: "working" }` / `{ status: "failed", error }` while the work runs or when it gave
85
+ * up (never over a delivery). CONFLICT before it is paid.
78
86
  */
79
87
  deliverCheckout(id: string, delivery: CheckoutDeliveryInput): Promise<CheckoutDelivery>;
80
88
  /** A reusable payment link for static pages (`<button data-village-pay="pl_…">` with pay.js). */
@@ -101,6 +109,7 @@ export declare class Village extends Ambasada {
101
109
  }>;
102
110
  /** Sends a `webhook.test` event now and returns the delivery. */
103
111
  testWebhook(): Promise<WebhookDelivery>;
112
+ /** Sends a failed or pending delivery again now (same `webhook-id`). CONFLICT for one already delivered: it never goes out twice. */
104
113
  retryWebhookDelivery(id: string): Promise<WebhookDelivery>;
105
114
  /** Buys one call of `company/offering` through the Gateway. Money moves only if the seller answers 2xx with valid output. */
106
115
  buy<Output = any>(offering: string, input: unknown, options?: BuyOptions): Promise<BuyResult<Output>>;
package/dist/index.js CHANGED
@@ -4,22 +4,6 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
- // src/client.ts
8
- import { Ambasada, AmbasadaError as AmbasadaError3 } from "@ambasada/sdk/platform";
9
-
10
- // src/errors.ts
11
- import { AmbasadaError, isAmbasadaError } from "@ambasada/sdk/platform";
12
- var VillageError = AmbasadaError;
13
- var isVillageError = isAmbasadaError;
14
- var ReceiptInvalidError = class extends AmbasadaError {
15
- reason;
16
- constructor(reason, message) {
17
- super("RECEIPT_INVALID", `payment required: ${message}`, 402, { reason });
18
- this.name = "ReceiptInvalidError";
19
- this.reason = reason;
20
- }
21
- };
22
-
23
7
  // ../protocol/src/index.ts
24
8
  var src_exports = {};
25
9
  __export(src_exports, {
@@ -53,18 +37,23 @@ __export(src_exports, {
53
37
  OFFERING_TIMEOUT_MS: () => OFFERING_TIMEOUT_MS,
54
38
  RECEIPT_HEADER: () => RECEIPT_HEADER,
55
39
  ReceiptError: () => ReceiptError,
40
+ SDK_SCRIPT_LIMIT: () => SDK_SCRIPT_LIMIT,
56
41
  SHELL_TOKEN_TTL_S: () => SHELL_TOKEN_TTL_S,
57
42
  SLOW_ENTRY_MS: () => SLOW_ENTRY_MS,
58
43
  SYSTEM_ACCOUNT: () => SYSTEM_ACCOUNT,
59
- VillageError: () => VillageError2,
44
+ UTILITY_CLIENT_MARGIN_MS: () => UTILITY_CLIENT_MARGIN_MS,
45
+ UTILITY_TIMEOUT_MS: () => UTILITY_TIMEOUT_MS,
46
+ VillageError: () => VillageError,
60
47
  WEBHOOK_HEADERS: () => WEBHOOK_HEADERS,
61
48
  WebhookError: () => WebhookError,
62
49
  appNotifyBody: () => appNotifyBody,
63
50
  appNotifyResult: () => appNotifyResult,
64
51
  appWebhookHeaders: () => appWebhookHeaders,
65
52
  base64url: () => base64url,
53
+ bundlesMiniappSdk: () => bundlesMiniappSdk,
66
54
  checkHtml: () => checkHtml,
67
55
  checkIcon: () => checkIcon,
56
+ checkPending: () => checkPending,
68
57
  checkRemote: () => checkRemote,
69
58
  createReceiptSigner: () => createReceiptSigner,
70
59
  decodeLeaf: () => decodeLeaf,
@@ -186,7 +175,7 @@ var ERROR_STATUS = {
186
175
  // Deploy-freeze and unattended windows: agents keep running, but config and steer are closed (DESIGN.md §5)
187
176
  FROZEN: 403
188
177
  };
189
- var VillageError2 = class extends Error {
178
+ var VillageError = class extends Error {
190
179
  code;
191
180
  status;
192
181
  details;
@@ -1274,7 +1263,7 @@ function checkHtml(html) {
1274
1263
  const darkStyle = [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi)].concat([...html.matchAll(/\bstyle\s*=\s*"([^"]*)"/gi)]).some(([, css]) => [...css.matchAll(/color-scheme\s*:\s*([^;}"]+)/gi)].some(([, v]) => /\bdark\b/i.test(v) && !/\blight\b/i.test(v)));
1275
1264
  if (darkMeta || darkStyle) f.problems.push({ code: "dark_only", message: "the entry page declares a dark-only color-scheme; the Ambasada app is light (use light, or light dark)" });
1276
1265
  if (!SDK_MARKERS.some((m) => html.includes(m))) {
1277
- f.warnings.push({ code: "no_miniapp_sdk", message: "no @ambasada/miniapp, miniapp.js or ambasadaShell in the entry HTML: a bundled import is fine too, the registry crawler confirms ready() is called at runtime" });
1266
+ f.warnings.push({ code: "no_miniapp_sdk", message: "no @ambasada/miniapp, miniapp.js or ambasadaShell in the entry HTML or its scripts: the Ambasada app waits for ready() to drop the splash" });
1278
1267
  }
1279
1268
  return f;
1280
1269
  }
@@ -1296,6 +1285,23 @@ async function probeManifest(origin, fetchImpl = fetch) {
1296
1285
  return { on: false, entry: null };
1297
1286
  }
1298
1287
  }
1288
+ var SDK_SCRIPT_LIMIT = 16;
1289
+ var BUNDLED_SDK = /ambasadaShell|@ambasada\/miniapp/;
1290
+ async function bundlesMiniappSdk(fetchImpl, pageUrl, html) {
1291
+ const origin = new URL(pageUrl).origin;
1292
+ const srcs = [...html.matchAll(/<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["']/gi)].map(([, src]) => URL.canParse(src, pageUrl) ? new URL(src, pageUrl) : null).filter((u) => !!u && u.origin === origin).slice(0, SDK_SCRIPT_LIMIT);
1293
+ const found = await Promise.all(
1294
+ srcs.map(async (u) => {
1295
+ try {
1296
+ const res = await get(fetchImpl, u.toString());
1297
+ return res.status === 200 && BUNDLED_SDK.test(await res.text());
1298
+ } catch {
1299
+ return false;
1300
+ }
1301
+ })
1302
+ );
1303
+ return found.includes(true);
1304
+ }
1299
1305
  async function checkPage(fetchImpl, url, report, html) {
1300
1306
  try {
1301
1307
  const started = Date.now();
@@ -1304,7 +1310,11 @@ async function checkPage(fetchImpl, url, report, html) {
1304
1310
  if (!html) await res.body?.cancel();
1305
1311
  const ms = Date.now() - started;
1306
1312
  if (res.status !== 200) report.problems.push({ code: "entry_status", message: `${url} answered ${res.status}${res.headers.get("location") ? ` \u2192 ${res.headers.get("location")}` : ""}; ${html ? "the entry" : "the app"} must answer 200 without redirects` });
1307
- else if (html) merge(report, checkHtml(text));
1313
+ else if (html) {
1314
+ const found = checkHtml(text);
1315
+ if (found.warnings.some((w) => w.code === "no_miniapp_sdk") && await bundlesMiniappSdk(fetchImpl, url, text)) found.warnings = found.warnings.filter((w) => w.code !== "no_miniapp_sdk");
1316
+ merge(report, found);
1317
+ }
1308
1318
  if (ms > SLOW_ENTRY_MS) report.warnings.push({ code: "slow_entry", message: `${url} took ${ms} ms; aim for under ${SLOW_ENTRY_MS} ms${html ? " (the splash stays until ready())" : ""}` });
1309
1319
  } catch (error) {
1310
1320
  report.problems.push({ code: "entry_fetch", message: `could not fetch ${url}: ${error.message}` });
@@ -1424,6 +1434,7 @@ var LABELS = [
1424
1434
  function findingLabel(code) {
1425
1435
  return LABELS.find(([re]) => re.test(code))?.[1] ?? code.replace(/_/g, " ");
1426
1436
  }
1437
+ var checkPending = (check) => check?.status === "pending";
1427
1438
  function summarizeCheck(report, now = /* @__PURE__ */ new Date()) {
1428
1439
  const issues = [
1429
1440
  ...report.problems.map((p) => ({ ...p, level: "problem", label: findingLabel(p.code) })),
@@ -1433,10 +1444,12 @@ function summarizeCheck(report, now = /* @__PURE__ */ new Date()) {
1433
1444
  const miniapp = report.miniapp ? { miniapp: report.miniapp.on ? "on" : "off", entry: report.miniapp.entry } : {};
1434
1445
  return { ok: report.ok, issues, needs, checked_at: now.toISOString(), ...miniapp };
1435
1446
  }
1436
- function listingLine(tier, check) {
1447
+ function listingLine(tier, check, kind = "company") {
1437
1448
  if (check?.miniapp === "off") return "Mini app: off";
1449
+ const listed = kind === "project" ? "Listed" : "Listed for the event";
1450
+ if (checkPending(check)) return `${tier === "verified" ? "Verified" : tier === "listed" ? listed : tier === "unlisted" ? "Unlisted" : "Not registered yet"} \xB7 checking\u2026`;
1438
1451
  if (tier === "verified") return "Verified";
1439
- if (tier === "listed") return "Listed for the event";
1452
+ if (tier === "listed") return listed;
1440
1453
  if (tier === "unlisted") return check?.needs.length ? `Unlisted \xB7 needs: ${check.needs.join(", ")}` : "Unlisted";
1441
1454
  return check?.needs.length ? `Not registered yet \xB7 needs: ${check.needs.join(", ")}` : "Not registered yet";
1442
1455
  }
@@ -1478,12 +1491,30 @@ var HEADERS = {
1478
1491
  };
1479
1492
  var MAX_DEPTH = 4;
1480
1493
  var OFFERING_TIMEOUT_MS = { min: 100, max: 3e4 };
1494
+ var UTILITY_TIMEOUT_MS = { llm: 12e4, image: 18e4, tool: 6e4 };
1495
+ var UTILITY_CLIENT_MARGIN_MS = 1e4;
1481
1496
  function offeringTimeoutProblem(value) {
1482
1497
  if (value === void 0) return null;
1483
1498
  const n = Number(value);
1484
1499
  return Number.isInteger(n) && n >= OFFERING_TIMEOUT_MS.min && n <= OFFERING_TIMEOUT_MS.max ? null : `timeout_ms must be between ${OFFERING_TIMEOUT_MS.min} and ${OFFERING_TIMEOUT_MS.max} (got ${JSON.stringify(value)})`;
1485
1500
  }
1486
1501
 
1502
+ // src/client.ts
1503
+ import { Ambasada, AmbasadaError as AmbasadaError3 } from "@ambasada/sdk/platform";
1504
+
1505
+ // src/errors.ts
1506
+ import { AmbasadaError, isAmbasadaError } from "@ambasada/sdk/platform";
1507
+ var VillageError2 = AmbasadaError;
1508
+ var isVillageError = isAmbasadaError;
1509
+ var ReceiptInvalidError = class extends AmbasadaError {
1510
+ reason;
1511
+ constructor(reason, message) {
1512
+ super("RECEIPT_INVALID", `payment required: ${message}`, 402, { reason });
1513
+ this.name = "ReceiptInvalidError";
1514
+ this.reason = reason;
1515
+ }
1516
+ };
1517
+
1487
1518
  // src/receipt.ts
1488
1519
  import { AmbasadaError as AmbasadaError2, getHeader } from "@ambasada/sdk/platform";
1489
1520
  var RECEIPT_HEADER2 = "x-village-receipt";
@@ -1600,6 +1631,11 @@ async function verifyWebhookPayload(secret, headers, body, options = {}) {
1600
1631
  }
1601
1632
 
1602
1633
  // src/client.ts
1634
+ var CLIENT_TIMEOUT_MS = {
1635
+ llm: UTILITY_TIMEOUT_MS.llm + UTILITY_CLIENT_MARGIN_MS,
1636
+ image: UTILITY_TIMEOUT_MS.image + UTILITY_CLIENT_MARGIN_MS,
1637
+ tool: UTILITY_TIMEOUT_MS.tool + UTILITY_CLIENT_MARGIN_MS
1638
+ };
1603
1639
  var DEFAULT_VILLAGE_API_URL = "https://hackaton.ambasada.id";
1604
1640
  var DEFAULT_MODEL = "deepseek-v4.1-flash";
1605
1641
  function randomKey() {
@@ -1728,7 +1764,9 @@ var Village = class extends Ambasada {
1728
1764
  }
1729
1765
  /**
1730
1766
  * Tells the village what a paid checkout delivered (`text` ≤ 4000 chars, an https `url`, or both): the person sees it
1731
- * with the purchase in their app and can rate it. Again for the same session replaces it. CONFLICT before it is paid.
1767
+ * with the purchase in their app (one push, the first time) and can rate it. The same delivery again changes nothing;
1768
+ * a new text replaces it. `{ status: "working" }` / `{ status: "failed", error }` while the work runs or when it gave
1769
+ * up (never over a delivery). CONFLICT before it is paid.
1732
1770
  */
1733
1771
  deliverCheckout(id, delivery) {
1734
1772
  return this.call("POST", `/v1/checkout/sessions/${encodeURIComponent(id)}/delivery`, { body: delivery });
@@ -1762,6 +1800,7 @@ var Village = class extends Ambasada {
1762
1800
  testWebhook() {
1763
1801
  return this.call("POST", "/v1/webhook/test");
1764
1802
  }
1803
+ /** Sends a failed or pending delivery again now (same `webhook-id`). CONFLICT for one already delivered: it never goes out twice. */
1765
1804
  retryWebhookDelivery(id) {
1766
1805
  return this.call("POST", `/v1/webhook/deliveries/${encodeURIComponent(id)}/retry`);
1767
1806
  }
@@ -1818,12 +1857,13 @@ var Village = class extends Ambasada {
1818
1857
  return res.choices[0]?.message.content ?? "";
1819
1858
  },
1820
1859
  {
1821
- chat: (req) => this.call("POST", "/v1/llm/chat", { body: { ...req, model: req.model ?? DEFAULT_MODEL } })
1860
+ // The API waits up to 120 s for the model (one that reasons first can take a minute): not the client's 45 s.
1861
+ chat: (req) => this.call("POST", "/v1/llm/chat", { body: { ...req, model: req.model ?? DEFAULT_MODEL }, timeoutMs: CLIENT_TIMEOUT_MS.llm })
1822
1862
  }
1823
1863
  );
1824
1864
  images = {
1825
1865
  /** Generates an image; the URL is re-hosted in the village files store. Default model: nano_banana. */
1826
- generate: (req) => this.call("POST", "/v1/images", { body: { ...req, model: req.model ?? "nano_banana" } })
1866
+ generate: (req) => this.call("POST", "/v1/images", { body: { ...req, model: req.model ?? "nano_banana" }, timeoutMs: CLIENT_TIMEOUT_MS.image })
1827
1867
  };
1828
1868
  tools = {
1829
1869
  /** The curated LLMOK tool catalog with TON prices. */
@@ -1836,6 +1876,8 @@ var Village = class extends Ambasada {
1836
1876
  const res = await this.send("POST", `/v1/tools/${encodeURIComponent(id)}`, {
1837
1877
  body: input ?? {},
1838
1878
  retry: true,
1879
+ // Image tools take as long as images; a routed search may try several providers within the API's 60 s.
1880
+ timeoutMs: id.startsWith("image.") ? CLIENT_TIMEOUT_MS.image : CLIENT_TIMEOUT_MS.tool,
1839
1881
  headers: { "idempotency-key": options.idempotencyKey ?? randomKey() }
1840
1882
  });
1841
1883
  const json = res.json;
@@ -1924,7 +1966,7 @@ export {
1924
1966
  RECEIPT_HEADER2 as RECEIPT_HEADER,
1925
1967
  ReceiptInvalidError,
1926
1968
  Village,
1927
- VillageError,
1969
+ VillageError2 as VillageError,
1928
1970
  WEBHOOK_HEADERS2 as WEBHOOK_HEADERS,
1929
1971
  WebhookInvalidError2 as WebhookInvalidError,
1930
1972
  createVillage,
package/dist/types.d.ts CHANGED
@@ -55,15 +55,26 @@ export interface CheckoutInput {
55
55
  /** Seconds, 300–86400 (default 3600). */
56
56
  expires_in?: number;
57
57
  }
58
- /** What a paid checkout delivered (`POST /v1/checkout/sessions/:id/delivery`): text, an https URL, or both. */
58
+ /**
59
+ * What a paid checkout delivered (`POST /v1/checkout/sessions/:id/delivery`): text, an https URL, or both. While the
60
+ * work runs (the templates' Jobs Durable Object), `status: "working"` (with `error`: why the last attempt failed, while
61
+ * it retries); `status: "failed"` with `error` when it gives up. `ambasada village deliveries` shows all three.
62
+ */
59
63
  export interface CheckoutDeliveryInput {
60
64
  text?: string;
61
65
  url?: string;
66
+ /** Default `delivered` (with text and/or url). */
67
+ status?: "working" | "delivered" | "failed";
68
+ /** Why an attempt failed (working: retrying) or why it gave up (failed); at most 300 characters are kept. */
69
+ error?: string;
62
70
  }
63
71
  export interface CheckoutDelivery {
64
72
  session: string;
73
+ /** `delivered` when the API predates delivery statuses. */
74
+ status?: "working" | "delivered" | "failed";
65
75
  text: string | null;
66
76
  url: string | null;
77
+ error?: string | null;
67
78
  at: string;
68
79
  }
69
80
  export interface CheckoutSession {
@@ -166,8 +177,18 @@ export interface ChatRequest {
166
177
  messages: ChatMessage[];
167
178
  temperature?: number;
168
179
  max_tokens?: number;
180
+ /** How much the model thinks before it answers; the village sends "low" when you leave it out. More thinking takes longer and more of `max_tokens`. */
181
+ reasoning_effort?: "low" | "medium" | "high" | (string & {});
182
+ /** `json_schema`: JSON in the schema's shape (live, and the mock's sample of it under `ambasada dev`); `json_object`: any JSON. */
169
183
  response_format?: {
170
184
  type: "text" | "json_object";
185
+ } | {
186
+ type: "json_schema";
187
+ json_schema: {
188
+ name: string;
189
+ schema: Record<string, unknown>;
190
+ strict?: boolean;
191
+ };
171
192
  } | Record<string, unknown>;
172
193
  [key: string]: unknown;
173
194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ambasada/village",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "The Hack A Ton add-on for Ambasada apps: TON checkout, receipts, offerings, LLM, the agent, the market; CLI commands and the local bank.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  "node": ">=22"
28
28
  },
29
29
  "dependencies": {
30
- "@ambasada/sdk": "^0.8.5"
30
+ "@ambasada/sdk": "^0.8.7"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.20.5",
@@ -35,7 +35,7 @@
35
35
  "typescript": "~5.9.3",
36
36
  "vitest": "^4.1.12",
37
37
  "@ambasada/protocol": "0.1.0",
38
- "@ambasada/cli": "0.8.5"
38
+ "@ambasada/cli": "0.8.7"
39
39
  },
40
40
  "author": "CENTYSO (https://centyso.com)",
41
41
  "homepage": "https://ambasada.id",