@ambasada/village 0.8.9 → 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/cli.mjs +148 -55
- package/dist/client.d.ts +11 -1
- package/dist/errors.d.ts +10 -0
- package/dist/index.js +80 -31
- package/dist/types.d.ts +11 -3
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -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>;
|
|
@@ -213,9 +215,17 @@ export declare class Village extends Ambasada {
|
|
|
213
215
|
decide: (id: string, decision: "approve" | "decline", reason?: string, options?: {
|
|
214
216
|
wait?: boolean;
|
|
215
217
|
}) => Promise<ChatDecision>;
|
|
216
|
-
|
|
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<{
|
|
217
225
|
entries: TranscriptEntry[];
|
|
218
226
|
next_since: number;
|
|
227
|
+
more?: number;
|
|
228
|
+
earlier?: number;
|
|
219
229
|
}>;
|
|
220
230
|
state: () => Promise<Record<string, unknown>>;
|
|
221
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(
|
|
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 =
|
|
1906
|
-
|
|
1907
|
-
const
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
}
|
|
1912
|
-
{
|
|
1913
|
-
|
|
1914
|
-
|
|
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.
|
|
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:
|
|
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). */
|
|
@@ -1996,7 +2041,11 @@ var Village = class extends Ambasada {
|
|
|
1996
2041
|
body: { decision, ...reason ? { reason } : {}, ...options.wait ? { wait: true } : {} },
|
|
1997
2042
|
...options.wait ? { timeoutMs: CHAT_WAIT_MS + 15e3 } : {}
|
|
1998
2043
|
}),
|
|
1999
|
-
|
|
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 } : {} } }),
|
|
2000
2049
|
state: () => this.call("GET", "/v1/agent/state")
|
|
2001
2050
|
};
|
|
2002
2051
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -192,7 +192,7 @@ export interface BuyResult<Output = any> {
|
|
|
192
192
|
job?: string;
|
|
193
193
|
}
|
|
194
194
|
/** What the village sells (apps/api/config/pricing.json); any other model is refused. */
|
|
195
|
-
export type ChatModel = "deepseek-v4.1-flash" | (string & {});
|
|
195
|
+
export type ChatModel = "deepseek-v4.1-flash" | "glm-5.3-flash" | (string & {});
|
|
196
196
|
export interface ChatMessage {
|
|
197
197
|
role: "system" | "user" | "assistant" | "tool";
|
|
198
198
|
content: string;
|
|
@@ -204,8 +204,11 @@ export interface ChatRequest {
|
|
|
204
204
|
messages: ChatMessage[];
|
|
205
205
|
temperature?: number;
|
|
206
206
|
max_tokens?: number;
|
|
207
|
-
/**
|
|
208
|
-
|
|
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 & {});
|
|
209
212
|
/** `json_schema`: JSON in the schema's shape (live, and the mock's sample of it under `ambasada dev`); `json_object`: any JSON. */
|
|
210
213
|
response_format?: {
|
|
211
214
|
type: "text" | "json_object";
|
|
@@ -232,7 +235,12 @@ export interface ChatResponse {
|
|
|
232
235
|
prompt_tokens: number;
|
|
233
236
|
completion_tokens: number;
|
|
234
237
|
total_tokens: number;
|
|
238
|
+
completion_tokens_details?: {
|
|
239
|
+
reasoning_tokens?: number;
|
|
240
|
+
};
|
|
235
241
|
};
|
|
242
|
+
/** On an empty answer cut by `max_tokens` (`finish_reason: "length"`): what to change. */
|
|
243
|
+
village_hint?: string;
|
|
236
244
|
[key: string]: unknown;
|
|
237
245
|
}
|
|
238
246
|
export interface ImageRequest {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ambasada/village",
|
|
3
|
-
"version": "0.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.
|
|
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.
|
|
38
|
+
"@ambasada/cli": "0.8.10"
|
|
39
39
|
},
|
|
40
40
|
"author": "CENTYSO (https://centyso.com)",
|
|
41
41
|
"homepage": "https://ambasada.id",
|