@ambasada/village 0.8.8 → 0.8.10

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,6 +1,6 @@
1
1
  import { Ambasada, type AmbasadaOptions } from "@ambasada/sdk/platform";
2
2
  import { type IncomingRequest } from "./receipt.js";
3
- import type { JobAnswer, 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";
3
+ import type { JobAnswer, AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutDelivery, CheckoutDeliveryInput, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, ChatLine, ChatProposal, ChatDecision, ChatTurn, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
4
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
5
  export declare const CLIENT_TIMEOUT_MS: {
6
6
  readonly llm: number;
@@ -157,6 +157,7 @@ export declare class Village extends Ambasada {
157
157
  /** Generates an image; the URL is re-hosted in the village files store. Default model: nano_banana. */
158
158
  generate: (req: ImageRequest) => Promise<ImageResult>;
159
159
  };
160
+ private generateImage;
160
161
  readonly tools: {
161
162
  /** The curated LLMOK tool catalog with TON prices. */
162
163
  list: () => Promise<ToolListing[]>;
@@ -165,6 +166,7 @@ export declare class Village extends Ambasada {
165
166
  idempotencyKey?: string;
166
167
  }) => Promise<ToolResult<Output>>;
167
168
  };
169
+ private callTool;
168
170
  readonly listings: {
169
171
  /** Creates or updates an offering (price changes are versioned). */
170
172
  put: (name: string, def: ListingInput) => Promise<Listing>;
@@ -206,11 +208,24 @@ export declare class Village extends Ambasada {
206
208
  }) => Promise<ChatLine[]>;
207
209
  /** Proposals waiting for your team. */
208
210
  proposals: () => Promise<ChatProposal[]>;
209
- /** Approve (it runs now) or decline a proposal. */
210
- decide: (id: string, decision: "approve" | "decline", reason?: string) => Promise<unknown>;
211
- transcript: (since?: number, limit?: number) => Promise<{
211
+ /**
212
+ * Approve (it runs now) or decline a proposal. `wait` (approve): also what the agent said after it, up to a
213
+ * minute (`follow_up`).
214
+ */
215
+ decide: (id: string, decision: "approve" | "decline", reason?: string, options?: {
216
+ wait?: boolean;
217
+ }) => Promise<ChatDecision>;
218
+ /**
219
+ * The agent's transcript after `since`, oldest first: the first `limit` entries, or with `newest` the last `limit`.
220
+ * `more`: entries after this page (read on from `next_since`); `earlier`: with `newest`, the ones before it.
221
+ */
222
+ transcript: (since?: number, limit?: number, options?: {
223
+ newest?: boolean;
224
+ }) => Promise<{
212
225
  entries: TranscriptEntry[];
213
226
  next_since: number;
227
+ more?: number;
228
+ earlier?: number;
214
229
  }>;
215
230
  state: () => Promise<Record<string, unknown>>;
216
231
  };
package/dist/errors.d.ts CHANGED
@@ -12,6 +12,16 @@ export type VillageError = AmbasadaError;
12
12
  /** `isAmbasadaError` under its 0.7 name. */
13
13
  export declare const isVillageError: typeof isAmbasadaError;
14
14
  export type ReceiptFailure = "malformed" | "unknown_key" | "bad_signature" | "expired" | "not_yet_valid" | "wrong_issuer" | "wrong_seller" | "wrong_offering" | "wrong_kind" | "wrong_session" | "body_mismatch" | "replayed" | "missing";
15
+ /** The stack above `fn` (the SDK method the product called): where the product made the call. */
16
+ export declare function callSite(fn: (...args: never[]) => unknown): string | undefined;
17
+ /**
18
+ * A failed utility call says which one it was and where the product made it (village dry run 6 #9: a provider error in
19
+ * a job pointed at `errorFromResponse (worker.js:87121:12)`, inside the SDK, and not at the call). The message ends with
20
+ * `(during tools.call web.search)` (job attempts and `ambasada logs` show it), `during` names the call, and the stack
21
+ * starts at the product's calling frame, which `ambasada logs` maps to its source (`at api/frost.ts:12`). An error
22
+ * already named keeps its name; `restack` gives it this call site (llm() through llm.chat).
23
+ */
24
+ export declare function duringCall(error: unknown, what: string, site: string | undefined, restack?: boolean): unknown;
15
25
  /** The request is not paid: no receipt, a forged or expired one, or one for another seller / offering / body. */
16
26
  export declare class ReceiptInvalidError extends AmbasadaError {
17
27
  readonly reason: ReceiptFailure;
package/dist/index.js CHANGED
@@ -148,6 +148,8 @@ var ERROR_STATUS = {
148
148
  // inbox texts: 20 an hour, 1 unsolicited per counterparty an hour (EXCHANGE.md §8)
149
149
  CAP_CHAT_RATE: 429,
150
150
  // turns of the CEO chat per company (docs/AGENT-CHAT.md)
151
+ CAP_TOOLS_RATE: 429,
152
+ // tool calls per company per minute (the provider's pool has no limit of its own)
151
153
  // Additions needed by the gateway and bank
152
154
  UPSTREAM_ERROR: 502,
153
155
  PRICE_ABOVE_MAX: 409,
@@ -174,6 +176,8 @@ var ERROR_STATUS = {
174
176
  // provider refused the model/tool/key (a configuration problem on our side). Never the caller's fault.
175
177
  PROVIDER_BUDGET: 503,
176
178
  PROVIDER_DENIED: 502,
179
+ // A tool or model whose supplier failed 3 times in a row: refused for 5 minutes without calling it (never charged)
180
+ TOOL_UNAVAILABLE: 503,
177
181
  // Deploy-freeze and unattended windows: agents keep running, but config and steer are closed (DESIGN.md §5)
178
182
  FROZEN: 403
179
183
  };
@@ -1518,6 +1522,24 @@ import { Ambasada, AmbasadaError as AmbasadaError3 } from "@ambasada/sdk/platfor
1518
1522
  import { AmbasadaError, isAmbasadaError } from "@ambasada/sdk/platform";
1519
1523
  var VillageError2 = AmbasadaError;
1520
1524
  var isVillageError = isAmbasadaError;
1525
+ function callSite(fn) {
1526
+ const site = {};
1527
+ Error.captureStackTrace?.(site, fn);
1528
+ return site.stack;
1529
+ }
1530
+ function duringCall(error, what, site, restack = false) {
1531
+ if (!(error instanceof Error)) return error;
1532
+ const e = error;
1533
+ if (e.during && !restack) return e;
1534
+ if (!e.during) {
1535
+ e.during = what;
1536
+ e.message = `${e.message} (during ${what})`;
1537
+ }
1538
+ const frames = (site ?? "").split("\n").filter((line) => line.trim().startsWith("at "));
1539
+ if (frames.length) e.stack = `${e.name}: ${e.message}
1540
+ ${frames.join("\n")}`;
1541
+ return e;
1542
+ }
1521
1543
  var ReceiptInvalidError = class extends AmbasadaError {
1522
1544
  reason;
1523
1545
  constructor(reason, message) {
@@ -1772,8 +1794,10 @@ var Village = class extends Ambasada {
1772
1794
  return async (req) => {
1773
1795
  const id = decodeURIComponent(new URL(req.url).pathname.split("/").filter(Boolean).at(-1) ?? "");
1774
1796
  try {
1797
+ const token = receiptHeader(req.headers);
1798
+ if (!token) throw new ReceiptInvalidError("missing", "missing x-village-receipt (the village Gateway polls this route)");
1775
1799
  const seller = await this.company();
1776
- const claims = await verifyCallReceipt(receiptHeader(req.headers), new Uint8Array(), { keys: this.keySource(), seller, offering: `${seller}/${offering2}` });
1800
+ const claims = await verifyCallReceipt(token, new Uint8Array(), { keys: this.keySource(), seller, offering: `${seller}/${offering2}` });
1777
1801
  if (claims.job !== id) throw new ReceiptInvalidError("wrong_kind", "this receipt is for another job");
1778
1802
  } catch (error) {
1779
1803
  if (error instanceof AmbasadaError3) return error.toResponse();
@@ -1902,22 +1926,40 @@ var Village = class extends Ambasada {
1902
1926
  * `village.llm("prompt")` → the answer text; `village.llm.chat(req)` → the full OpenAI-style response.
1903
1927
  * Default model: deepseek-v4.1-flash.
1904
1928
  */
1905
- llm = Object.assign(
1906
- async (prompt, options = {}) => {
1907
- const { system, ...rest } = options;
1908
- const messages = system ? [{ role: "system", content: system }] : [];
1909
- const res = await this.llm.chat({ ...rest, messages: [...messages, { role: "user", content: prompt }] });
1910
- return res.choices[0]?.message.content ?? "";
1911
- },
1912
- {
1913
- // The API waits up to 120 s for the model (one that reasons first can take a minute): not the client's 45 s.
1914
- chat: (req) => this.call("POST", "/v1/llm/chat", { body: { ...req, model: req.model ?? DEFAULT_MODEL }, timeoutMs: CLIENT_TIMEOUT_MS.llm })
1915
- }
1916
- );
1929
+ llm = (() => {
1930
+ const chat = (req) => {
1931
+ const site = callSite(chat);
1932
+ return this.call("POST", "/v1/llm/chat", { body: { ...req, model: req.model ?? DEFAULT_MODEL }, timeoutMs: CLIENT_TIMEOUT_MS.llm }).catch((e) => {
1933
+ throw duringCall(e, "llm.chat", site);
1934
+ });
1935
+ };
1936
+ const llm = async (prompt, options = {}) => {
1937
+ const site = callSite(llm);
1938
+ try {
1939
+ const { system, ...rest } = options;
1940
+ const messages = system ? [{ role: "system", content: system }] : [];
1941
+ const res = await chat({ ...rest, messages: [...messages, { role: "user", content: prompt }] });
1942
+ const choice = res.choices[0];
1943
+ if (!choice?.message.content?.trim() && choice?.finish_reason === "length") {
1944
+ const hint = typeof res.village_hint === "string" ? res.village_hint : `the model ran out of max_tokens before it answered (nothing was charged): raise max_tokens (8192+ with reasoning on), ask for less, or turn reasoning off (reasoning_effort: "none")`;
1945
+ throw new AmbasadaError3("UPSTREAM_ERROR", `llm: ${hint}`, 502, { finish_reason: "length" });
1946
+ }
1947
+ return choice?.message.content ?? "";
1948
+ } catch (e) {
1949
+ throw duringCall(e, "llm.chat", site, true);
1950
+ }
1951
+ };
1952
+ return Object.assign(llm, { chat });
1953
+ })();
1917
1954
  images = {
1918
1955
  /** Generates an image; the URL is re-hosted in the village files store. Default model: nano_banana. */
1919
- generate: (req) => this.call("POST", "/v1/images", { body: { ...req, model: req.model ?? "nano_banana" }, timeoutMs: CLIENT_TIMEOUT_MS.image })
1956
+ generate: (req) => this.generateImage(req, callSite(this.images.generate))
1920
1957
  };
1958
+ generateImage(req, site) {
1959
+ return this.call("POST", "/v1/images", { body: { ...req, model: req.model ?? "nano_banana" }, timeoutMs: CLIENT_TIMEOUT_MS.image }).catch((e) => {
1960
+ throw duringCall(e, `images.generate ${req.model ?? "nano_banana"}`, site);
1961
+ });
1962
+ }
1921
1963
  tools = {
1922
1964
  /** The curated LLMOK tool catalog with TON prices. */
1923
1965
  list: async () => {
@@ -1925,23 +1967,26 @@ var Village = class extends Ambasada {
1925
1967
  return Array.isArray(res) ? res : res.tools;
1926
1968
  },
1927
1969
  /** Runs one tool; debits your wallet only on success. Replays with the same idempotency key are free. */
1928
- call: async (id, input, options = {}) => {
1929
- const res = await this.send("POST", `/v1/tools/${encodeURIComponent(id)}`, {
1930
- body: input ?? {},
1931
- retry: true,
1932
- // Image tools take as long as images; a routed search may try several providers within the API's 60 s.
1933
- timeoutMs: id.startsWith("image.") ? CLIENT_TIMEOUT_MS.image : CLIENT_TIMEOUT_MS.tool,
1934
- headers: { "idempotency-key": options.idempotencyKey ?? randomKey() }
1935
- });
1936
- const json = res.json;
1937
- return {
1938
- output: json.output,
1939
- callId: json.call_id ?? res.headers.get("x-village-call-id"),
1940
- amount: json.cost ?? num(res.headers.get("x-village-amount")),
1941
- replayed: res.headers.get("idempotent-replayed") === "true"
1942
- };
1943
- }
1970
+ call: (id, input, options = {}) => this.callTool(id, input, options, callSite(this.tools.call))
1944
1971
  };
1972
+ async callTool(id, input, options, site) {
1973
+ const res = await this.send("POST", `/v1/tools/${encodeURIComponent(id)}`, {
1974
+ body: input ?? {},
1975
+ retry: true,
1976
+ // Image tools take as long as images; a routed search may try several providers within the API's 60 s.
1977
+ timeoutMs: id.startsWith("image.") ? CLIENT_TIMEOUT_MS.image : CLIENT_TIMEOUT_MS.tool,
1978
+ headers: { "idempotency-key": options.idempotencyKey ?? randomKey() }
1979
+ }).catch((e) => {
1980
+ throw duringCall(e, `tools.call ${id}`, site);
1981
+ });
1982
+ const json = res.json;
1983
+ return {
1984
+ output: json.output,
1985
+ callId: json.call_id ?? res.headers.get("x-village-call-id"),
1986
+ amount: json.cost ?? num(res.headers.get("x-village-amount")),
1987
+ replayed: res.headers.get("idempotent-replayed") === "true"
1988
+ };
1989
+ }
1945
1990
  // ── listings, wallet, ledger ──────────────────────────────────────────────
1946
1991
  listings = {
1947
1992
  /** Creates or updates an offering (price changes are versioned). */
@@ -1988,9 +2033,19 @@ var Village = class extends Ambasada {
1988
2033
  messages: async (options = {}) => (await this.call("GET", "/v1/agent/chat/messages", { query: { limit: options.limit ?? 50, ...options.since ? { since: options.since } : {} } })).messages,
1989
2034
  /** Proposals waiting for your team. */
1990
2035
  proposals: async () => (await this.call("GET", "/v1/agent/chat/proposals")).proposals,
1991
- /** Approve (it runs now) or decline a proposal. */
1992
- decide: (id, decision, reason) => this.call("POST", `/v1/agent/chat/proposals/${encodeURIComponent(id)}`, { body: { decision, ...reason ? { reason } : {} } }),
1993
- transcript: (since = 0, limit = 200) => this.call("GET", "/v1/agent/transcript", { query: { since, limit } }),
2036
+ /**
2037
+ * Approve (it runs now) or decline a proposal. `wait` (approve): also what the agent said after it, up to a
2038
+ * minute (`follow_up`).
2039
+ */
2040
+ decide: (id, decision, reason, options = {}) => this.call("POST", `/v1/agent/chat/proposals/${encodeURIComponent(id)}`, {
2041
+ body: { decision, ...reason ? { reason } : {}, ...options.wait ? { wait: true } : {} },
2042
+ ...options.wait ? { timeoutMs: CHAT_WAIT_MS + 15e3 } : {}
2043
+ }),
2044
+ /**
2045
+ * The agent's transcript after `since`, oldest first: the first `limit` entries, or with `newest` the last `limit`.
2046
+ * `more`: entries after this page (read on from `next_since`); `earlier`: with `newest`, the ones before it.
2047
+ */
2048
+ transcript: (since = 0, limit = 200, options = {}) => this.call("GET", "/v1/agent/transcript", { query: { since, limit, ...options.newest ? { newest: 1 } : {} } }),
1994
2049
  state: () => this.call("GET", "/v1/agent/state")
1995
2050
  };
1996
2051
  };
package/dist/types.d.ts CHANGED
@@ -17,6 +17,8 @@ export interface ReceiptClaims {
17
17
  /** TON you may still spend downstream on behalf of this call. */
18
18
  budget?: number;
19
19
  deal_id?: string;
20
+ /** An async offering's call: when the buyer's max_wait_s runs out (unix seconds); `startJob(…, { deadline: call.deadline * 1000 })`. */
21
+ deadline?: number;
20
22
  body_sha256: string;
21
23
  iat: number;
22
24
  exp: number;
@@ -156,8 +158,14 @@ export interface BuyOptions {
156
158
  wait?: boolean;
157
159
  }
158
160
  /** What an async offering's `GET <endpoint>/jobs/<id>` says (village.jobStatus): the Gateway settles on done or failed. */
159
- export type JobAnswer = {
161
+ export type JobAnswer =
162
+ /** `attempts`: the failed ones so far (the template's `jobAnswer(job)`); the buyer sees "working (attempt 2, last error: …)". */
163
+ {
160
164
  status: "working";
165
+ attempts?: {
166
+ at: string;
167
+ error: string;
168
+ }[];
161
169
  } | {
162
170
  status: "done";
163
171
  output: unknown;
@@ -184,7 +192,7 @@ export interface BuyResult<Output = any> {
184
192
  job?: string;
185
193
  }
186
194
  /** What the village sells (apps/api/config/pricing.json); any other model is refused. */
187
- export type ChatModel = "deepseek-v4.1-flash" | (string & {});
195
+ export type ChatModel = "deepseek-v4.1-flash" | "glm-5.3-flash" | (string & {});
188
196
  export interface ChatMessage {
189
197
  role: "system" | "user" | "assistant" | "tool";
190
198
  content: string;
@@ -196,8 +204,11 @@ export interface ChatRequest {
196
204
  messages: ChatMessage[];
197
205
  temperature?: number;
198
206
  max_tokens?: number;
199
- /** 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`. */
200
- reasoning_effort?: "low" | "medium" | "high" | (string & {});
207
+ /**
208
+ * deepseek-v4.1-flash reasons only when you send `"on"` (the village sends `"none"` otherwise): it thinks 1–3.5k
209
+ * tokens first, inside `max_tokens`, which is then at least 8192. glm-5.3-flash always reasons.
210
+ */
211
+ reasoning_effort?: "none" | "on" | (string & {});
201
212
  /** `json_schema`: JSON in the schema's shape (live, and the mock's sample of it under `ambasada dev`); `json_object`: any JSON. */
202
213
  response_format?: {
203
214
  type: "text" | "json_object";
@@ -224,7 +235,12 @@ export interface ChatResponse {
224
235
  prompt_tokens: number;
225
236
  completion_tokens: number;
226
237
  total_tokens: number;
238
+ completion_tokens_details?: {
239
+ reasoning_tokens?: number;
240
+ };
227
241
  };
242
+ /** On an empty answer cut by `max_tokens` (`finish_reason: "length"`): what to change. */
243
+ village_hint?: string;
228
244
  [key: string]: unknown;
229
245
  }
230
246
  export interface ImageRequest {
@@ -394,9 +410,22 @@ export interface ChatTurn {
394
410
  /** With `wait`: how the turn ended and the agent's reply. */
395
411
  status?: "completed" | "error" | "aborted" | "skipped" | "pending" | "running";
396
412
  error?: string;
397
- /** Still waiting: "wake" while the agent's wake runs (the turn is answered after it). */
413
+ /** Still waiting: "wake" while the agent's wake runs (the turn is answered after it); also on a `wait: false` send during a wake. */
398
414
  queued?: "wake";
399
415
  reply?: ChatLine | null;
416
+ /** The turn ended and the agent said nothing, even after the village asked it once more: "No reply (the model returned nothing)". */
417
+ no_reply?: string;
418
+ }
419
+ /** `agent.decide`: what ran, and with `wait` (approve) what the agent said after it. */
420
+ export interface ChatDecision {
421
+ id: string;
422
+ decision: "approve" | "decline";
423
+ /** The action's outcome (approve): its result, or `{ error: { code, message } }` when it failed. */
424
+ result: unknown;
425
+ /** The API's time of the decision (ms). */
426
+ at?: number;
427
+ /** With `wait`: the agent's next line (its reply, or the "no reply" card), null when a minute went by. */
428
+ follow_up?: ChatLine | null;
400
429
  }
401
430
  export interface MarketOffering {
402
431
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ambasada/village",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
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.8"
30
+ "@ambasada/sdk": "^0.8.10"
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.8"
38
+ "@ambasada/cli": "0.8.10"
39
39
  },
40
40
  "author": "CENTYSO (https://centyso.com)",
41
41
  "homepage": "https://ambasada.id",