@ambasada/village 0.8.6 → 0.8.8
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 +802 -87
- package/dist/client.d.ts +43 -4
- package/dist/define.d.ts +9 -1
- package/dist/index.js +124 -34
- package/dist/types.d.ts +40 -2
- package/package.json +3 -3
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
|
-
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";
|
|
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";
|
|
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 →
|
|
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
|
|
@@ -52,6 +58,22 @@ export declare class Village extends Ambasada {
|
|
|
52
58
|
* export default village.sell("translate", async ({ text, to }) => ({ text: await village.llm(`…`) }));
|
|
53
59
|
*/
|
|
54
60
|
sell<Input = any, Output = unknown>(offering: string, handler: SellHandler<Input, Output>): (req: Request) => Promise<Response>;
|
|
61
|
+
/**
|
|
62
|
+
* An async offering's answer to a paid call (`async: true` in offerings.ts): the work goes on as job `id` (in the
|
|
63
|
+
* template, a Jobs Durable Object named after it), and the Gateway asks `GET <endpoint>/jobs/<id>` until it is done.
|
|
64
|
+
*
|
|
65
|
+
* export default village.sell("ideas", async (input, call) => {
|
|
66
|
+
* await startJob(env, `ideas:${call.call_id}`, "ideas", input);
|
|
67
|
+
* return village.deferred(`ideas:${call.call_id}`);
|
|
68
|
+
* });
|
|
69
|
+
*/
|
|
70
|
+
deferred(id: string): Response;
|
|
71
|
+
/**
|
|
72
|
+
* `GET <endpoint>/jobs/<id>` of an async offering: checks that the Gateway asks (its signed receipt names this
|
|
73
|
+
* job), then answers with what `lookup(id)` says: `{ status: "working" }`, `{ status: "done", output }` (the buyer
|
|
74
|
+
* pays and gets `output`) or `{ status: "failed", error }` (the buyer is refunded). `null`: no such job (404).
|
|
75
|
+
*/
|
|
76
|
+
jobStatus(offering: string, lookup: (id: string, req: Request) => Promise<JobAnswer | null> | JobAnswer | null): (req: Request) => Promise<Response>;
|
|
55
77
|
/** Verifies a signed webhook (`checkout.paid`, `webhook.test`) and returns the event. Throws `WebhookInvalidError`. */
|
|
56
78
|
verifyWebhook<Data = any>(req: IncomingRequest, secret?: string): Promise<WebhookEvent<Data>>;
|
|
57
79
|
/** Verifies a checkout receipt (pay.js `village:paid` → `e.detail.receipt`, or `session.receipt`). */
|
|
@@ -74,7 +96,9 @@ export declare class Village extends Ambasada {
|
|
|
74
96
|
}>;
|
|
75
97
|
/**
|
|
76
98
|
* 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.
|
|
99
|
+
* with the purchase in their app (one push, the first time) and can rate it. The same delivery again changes nothing;
|
|
100
|
+
* a new text replaces it. `{ status: "working" }` / `{ status: "failed", error }` while the work runs or when it gave
|
|
101
|
+
* up (never over a delivery). CONFLICT before it is paid.
|
|
78
102
|
*/
|
|
79
103
|
deliverCheckout(id: string, delivery: CheckoutDeliveryInput): Promise<CheckoutDelivery>;
|
|
80
104
|
/** A reusable payment link for static pages (`<button data-village-pay="pl_…">` with pay.js). */
|
|
@@ -101,6 +125,7 @@ export declare class Village extends Ambasada {
|
|
|
101
125
|
}>;
|
|
102
126
|
/** Sends a `webhook.test` event now and returns the delivery. */
|
|
103
127
|
testWebhook(): Promise<WebhookDelivery>;
|
|
128
|
+
/** Sends a failed or pending delivery again now (same `webhook-id`). CONFLICT for one already delivered: it never goes out twice. */
|
|
104
129
|
retryWebhookDelivery(id: string): Promise<WebhookDelivery>;
|
|
105
130
|
/** Buys one call of `company/offering` through the Gateway. Money moves only if the seller answers 2xx with valid output. */
|
|
106
131
|
buy<Output = any>(offering: string, input: unknown, options?: BuyOptions): Promise<BuyResult<Output>>;
|
|
@@ -110,6 +135,15 @@ export declare class Village extends Ambasada {
|
|
|
110
135
|
*/
|
|
111
136
|
need<Output = any>(name: string, input: unknown, options?: BuyOptions): Promise<BuyResult<Output>>;
|
|
112
137
|
private paid;
|
|
138
|
+
/**
|
|
139
|
+
* An async purchase (`buy` with `wait: false`, or one an agent started): waits for its result, long-polling the
|
|
140
|
+
* Gateway until the seller's job is done (then charged) or its time is up (refunded: throws the error, TIMEOUT).
|
|
141
|
+
* `wait: false`: one look, `pending: true` while it runs.
|
|
142
|
+
*/
|
|
143
|
+
buyJob<Output = any>(job: string, options?: {
|
|
144
|
+
wait?: boolean;
|
|
145
|
+
signal?: AbortSignal;
|
|
146
|
+
}): Promise<BuyResult<Output>>;
|
|
113
147
|
/**
|
|
114
148
|
* `village.llm("prompt")` → the answer text; `village.llm.chat(req)` → the full OpenAI-style response.
|
|
115
149
|
* Default model: deepseek-v4.1-flash.
|
|
@@ -154,12 +188,17 @@ export declare class Village extends Ambasada {
|
|
|
154
188
|
steer: (text: string) => Promise<unknown>;
|
|
155
189
|
/**
|
|
156
190
|
* Talk to your agent (docs/AGENT-CHAT.md). With `wait` (the default) it resolves with the reply, up to a
|
|
157
|
-
* minute; the same `idempotencyKey` is the same turn, so a retry never asks twice.
|
|
191
|
+
* minute; the same `idempotencyKey` is the same turn, so a retry never asks twice. A reply later than that
|
|
192
|
+
* (`reply: null`, status `pending`/`running`; `queued: "wake"` while the agent's wake runs): `turn(submission_id)`.
|
|
158
193
|
*/
|
|
159
194
|
chat: (text: string, options?: {
|
|
160
195
|
idempotencyKey?: string;
|
|
161
196
|
wait?: boolean;
|
|
162
197
|
}) => Promise<ChatTurn>;
|
|
198
|
+
/** A turn sent before and its reply, waiting up to `waitSeconds` (0–60, default 60) for one still running. */
|
|
199
|
+
turn: (submissionId: string, options?: {
|
|
200
|
+
waitSeconds?: number;
|
|
201
|
+
}) => Promise<ChatTurn>;
|
|
163
202
|
/** The chat, oldest first: your words, the agent's replies and proposals, and cards for what happened. */
|
|
164
203
|
messages: (options?: {
|
|
165
204
|
since?: number;
|
package/dist/define.d.ts
CHANGED
|
@@ -38,8 +38,16 @@ export interface OfferingDef {
|
|
|
38
38
|
output?: FieldSchema;
|
|
39
39
|
/** Allow a free sample (`market.preview`). */
|
|
40
40
|
preview?: boolean;
|
|
41
|
-
/** How long the Gateway waits for your answer, ms: 100–30000 (default 30000, the most). Longer work:
|
|
41
|
+
/** How long the Gateway waits for your answer, ms: 100–30000 (default 30000, the most). Longer work: `async`. */
|
|
42
42
|
timeout_ms?: number;
|
|
43
43
|
status?: "open" | "paused";
|
|
44
|
+
/**
|
|
45
|
+
* Work that takes longer than 30 s (a search and a model call, a report): the handler answers at once with
|
|
46
|
+
* `village.deferred(jobId)` and `GET <endpoint>/jobs/<id>` says how it is going (`village.jobStatus`). The Gateway
|
|
47
|
+
* polls it and the buyer pays when it is done; not done within `max_wait_s`, the buyer is refunded.
|
|
48
|
+
*/
|
|
49
|
+
async?: boolean;
|
|
50
|
+
/** Async: seconds the Gateway waits for the job (10–600, default 300). */
|
|
51
|
+
max_wait_s?: number;
|
|
44
52
|
}
|
|
45
53
|
export declare function offering(def: OfferingDef): OfferingDef;
|
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, {
|
|
@@ -28,6 +12,7 @@ __export(src_exports, {
|
|
|
28
12
|
APP_SIGNATURE_HEADER: () => APP_SIGNATURE_HEADER,
|
|
29
13
|
APP_TIMESTAMP_HEADER: () => APP_TIMESTAMP_HEADER,
|
|
30
14
|
APP_WEBHOOK_TOLERANCE_S: () => APP_WEBHOOK_TOLERANCE_S,
|
|
15
|
+
ASYNC_OFFERING: () => ASYNC_OFFERING,
|
|
31
16
|
AppWebhookError: () => AppWebhookError,
|
|
32
17
|
DEFAULT_HUB_URL: () => DEFAULT_HUB_URL,
|
|
33
18
|
DEFAULT_ISSUER: () => DEFAULT_ISSUER,
|
|
@@ -57,12 +42,15 @@ __export(src_exports, {
|
|
|
57
42
|
SHELL_TOKEN_TTL_S: () => SHELL_TOKEN_TTL_S,
|
|
58
43
|
SLOW_ENTRY_MS: () => SLOW_ENTRY_MS,
|
|
59
44
|
SYSTEM_ACCOUNT: () => SYSTEM_ACCOUNT,
|
|
60
|
-
|
|
45
|
+
UTILITY_CLIENT_MARGIN_MS: () => UTILITY_CLIENT_MARGIN_MS,
|
|
46
|
+
UTILITY_TIMEOUT_MS: () => UTILITY_TIMEOUT_MS,
|
|
47
|
+
VillageError: () => VillageError,
|
|
61
48
|
WEBHOOK_HEADERS: () => WEBHOOK_HEADERS,
|
|
62
49
|
WebhookError: () => WebhookError,
|
|
63
50
|
appNotifyBody: () => appNotifyBody,
|
|
64
51
|
appNotifyResult: () => appNotifyResult,
|
|
65
52
|
appWebhookHeaders: () => appWebhookHeaders,
|
|
53
|
+
asyncOfferingProblem: () => asyncOfferingProblem,
|
|
66
54
|
base64url: () => base64url,
|
|
67
55
|
bundlesMiniappSdk: () => bundlesMiniappSdk,
|
|
68
56
|
checkHtml: () => checkHtml,
|
|
@@ -189,7 +177,7 @@ var ERROR_STATUS = {
|
|
|
189
177
|
// Deploy-freeze and unattended windows: agents keep running, but config and steer are closed (DESIGN.md §5)
|
|
190
178
|
FROZEN: 403
|
|
191
179
|
};
|
|
192
|
-
var
|
|
180
|
+
var VillageError = class extends Error {
|
|
193
181
|
code;
|
|
194
182
|
status;
|
|
195
183
|
details;
|
|
@@ -1505,12 +1493,40 @@ var HEADERS = {
|
|
|
1505
1493
|
};
|
|
1506
1494
|
var MAX_DEPTH = 4;
|
|
1507
1495
|
var OFFERING_TIMEOUT_MS = { min: 100, max: 3e4 };
|
|
1496
|
+
var ASYNC_OFFERING = { defaultMaxWaitS: 300, minMaxWaitS: 10, maxMaxWaitS: 600 };
|
|
1497
|
+
function asyncOfferingProblem(isAsync, maxWaitS) {
|
|
1498
|
+
if (isAsync !== void 0 && typeof isAsync !== "boolean") return "async must be true or false";
|
|
1499
|
+
if (maxWaitS === void 0 || maxWaitS === null) return null;
|
|
1500
|
+
if (isAsync !== true) return "max_wait_s needs async: true";
|
|
1501
|
+
if (typeof maxWaitS !== "number" || !Number.isInteger(maxWaitS) || maxWaitS < ASYNC_OFFERING.minMaxWaitS || maxWaitS > ASYNC_OFFERING.maxMaxWaitS) {
|
|
1502
|
+
return `max_wait_s must be whole seconds between ${ASYNC_OFFERING.minMaxWaitS} and ${ASYNC_OFFERING.maxMaxWaitS}`;
|
|
1503
|
+
}
|
|
1504
|
+
return null;
|
|
1505
|
+
}
|
|
1506
|
+
var UTILITY_TIMEOUT_MS = { llm: 12e4, image: 18e4, tool: 6e4 };
|
|
1507
|
+
var UTILITY_CLIENT_MARGIN_MS = 1e4;
|
|
1508
1508
|
function offeringTimeoutProblem(value) {
|
|
1509
1509
|
if (value === void 0) return null;
|
|
1510
1510
|
const n = Number(value);
|
|
1511
1511
|
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)})`;
|
|
1512
1512
|
}
|
|
1513
1513
|
|
|
1514
|
+
// src/client.ts
|
|
1515
|
+
import { Ambasada, AmbasadaError as AmbasadaError3 } from "@ambasada/sdk/platform";
|
|
1516
|
+
|
|
1517
|
+
// src/errors.ts
|
|
1518
|
+
import { AmbasadaError, isAmbasadaError } from "@ambasada/sdk/platform";
|
|
1519
|
+
var VillageError2 = AmbasadaError;
|
|
1520
|
+
var isVillageError = isAmbasadaError;
|
|
1521
|
+
var ReceiptInvalidError = class extends AmbasadaError {
|
|
1522
|
+
reason;
|
|
1523
|
+
constructor(reason, message) {
|
|
1524
|
+
super("RECEIPT_INVALID", `payment required: ${message}`, 402, { reason });
|
|
1525
|
+
this.name = "ReceiptInvalidError";
|
|
1526
|
+
this.reason = reason;
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1514
1530
|
// src/receipt.ts
|
|
1515
1531
|
import { AmbasadaError as AmbasadaError2, getHeader } from "@ambasada/sdk/platform";
|
|
1516
1532
|
var RECEIPT_HEADER2 = "x-village-receipt";
|
|
@@ -1627,6 +1643,12 @@ async function verifyWebhookPayload(secret, headers, body, options = {}) {
|
|
|
1627
1643
|
}
|
|
1628
1644
|
|
|
1629
1645
|
// src/client.ts
|
|
1646
|
+
var CLIENT_TIMEOUT_MS = {
|
|
1647
|
+
llm: UTILITY_TIMEOUT_MS.llm + UTILITY_CLIENT_MARGIN_MS,
|
|
1648
|
+
image: UTILITY_TIMEOUT_MS.image + UTILITY_CLIENT_MARGIN_MS,
|
|
1649
|
+
tool: UTILITY_TIMEOUT_MS.tool + UTILITY_CLIENT_MARGIN_MS
|
|
1650
|
+
};
|
|
1651
|
+
var CHAT_WAIT_MS = 6e4;
|
|
1630
1652
|
var DEFAULT_VILLAGE_API_URL = "https://hackaton.ambasada.id";
|
|
1631
1653
|
var DEFAULT_MODEL = "deepseek-v4.1-flash";
|
|
1632
1654
|
function randomKey() {
|
|
@@ -1729,6 +1751,39 @@ var Village = class extends Ambasada {
|
|
|
1729
1751
|
}
|
|
1730
1752
|
};
|
|
1731
1753
|
}
|
|
1754
|
+
/**
|
|
1755
|
+
* An async offering's answer to a paid call (`async: true` in offerings.ts): the work goes on as job `id` (in the
|
|
1756
|
+
* template, a Jobs Durable Object named after it), and the Gateway asks `GET <endpoint>/jobs/<id>` until it is done.
|
|
1757
|
+
*
|
|
1758
|
+
* export default village.sell("ideas", async (input, call) => {
|
|
1759
|
+
* await startJob(env, `ideas:${call.call_id}`, "ideas", input);
|
|
1760
|
+
* return village.deferred(`ideas:${call.call_id}`);
|
|
1761
|
+
* });
|
|
1762
|
+
*/
|
|
1763
|
+
deferred(id) {
|
|
1764
|
+
return Response.json({ job: id, status: "working" }, { status: 202 });
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* `GET <endpoint>/jobs/<id>` of an async offering: checks that the Gateway asks (its signed receipt names this
|
|
1768
|
+
* job), then answers with what `lookup(id)` says: `{ status: "working" }`, `{ status: "done", output }` (the buyer
|
|
1769
|
+
* pays and gets `output`) or `{ status: "failed", error }` (the buyer is refunded). `null`: no such job (404).
|
|
1770
|
+
*/
|
|
1771
|
+
jobStatus(offering2, lookup) {
|
|
1772
|
+
return async (req) => {
|
|
1773
|
+
const id = decodeURIComponent(new URL(req.url).pathname.split("/").filter(Boolean).at(-1) ?? "");
|
|
1774
|
+
try {
|
|
1775
|
+
const seller = await this.company();
|
|
1776
|
+
const claims = await verifyCallReceipt(receiptHeader(req.headers), new Uint8Array(), { keys: this.keySource(), seller, offering: `${seller}/${offering2}` });
|
|
1777
|
+
if (claims.job !== id) throw new ReceiptInvalidError("wrong_kind", "this receipt is for another job");
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
if (error instanceof AmbasadaError3) return error.toResponse();
|
|
1780
|
+
throw error;
|
|
1781
|
+
}
|
|
1782
|
+
const answer = await lookup(id, req);
|
|
1783
|
+
if (!answer) return Response.json({ error: { code: "NOT_FOUND", message: `no job ${id}` } }, { status: 404 });
|
|
1784
|
+
return Response.json(answer, { status: answer.status === "working" ? 202 : 200 });
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1732
1787
|
/** Verifies a signed webhook (`checkout.paid`, `webhook.test`) and returns the event. Throws `WebhookInvalidError`. */
|
|
1733
1788
|
async verifyWebhook(req, secret) {
|
|
1734
1789
|
const { headers, body } = await readIncoming(req);
|
|
@@ -1755,7 +1810,9 @@ var Village = class extends Ambasada {
|
|
|
1755
1810
|
}
|
|
1756
1811
|
/**
|
|
1757
1812
|
* Tells the village what a paid checkout delivered (`text` ≤ 4000 chars, an https `url`, or both): the person sees it
|
|
1758
|
-
* with the purchase in their app and can rate it.
|
|
1813
|
+
* with the purchase in their app (one push, the first time) and can rate it. The same delivery again changes nothing;
|
|
1814
|
+
* a new text replaces it. `{ status: "working" }` / `{ status: "failed", error }` while the work runs or when it gave
|
|
1815
|
+
* up (never over a delivery). CONFLICT before it is paid.
|
|
1759
1816
|
*/
|
|
1760
1817
|
deliverCheckout(id, delivery) {
|
|
1761
1818
|
return this.call("POST", `/v1/checkout/sessions/${encodeURIComponent(id)}/delivery`, { body: delivery });
|
|
@@ -1789,6 +1846,7 @@ var Village = class extends Ambasada {
|
|
|
1789
1846
|
testWebhook() {
|
|
1790
1847
|
return this.call("POST", "/v1/webhook/test");
|
|
1791
1848
|
}
|
|
1849
|
+
/** Sends a failed or pending delivery again now (same `webhook-id`). CONFLICT for one already delivered: it never goes out twice. */
|
|
1792
1850
|
retryWebhookDelivery(id) {
|
|
1793
1851
|
return this.call("POST", `/v1/webhook/deliveries/${encodeURIComponent(id)}/retry`);
|
|
1794
1852
|
}
|
|
@@ -1821,16 +1879,23 @@ var Village = class extends Ambasada {
|
|
|
1821
1879
|
"x-village-reason": options.reason?.slice(0, 280)
|
|
1822
1880
|
}
|
|
1823
1881
|
});
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1882
|
+
if (res.status === 202) {
|
|
1883
|
+
const job = res.json?.job;
|
|
1884
|
+
if (typeof job === "string") return options.wait === false ? pendingResult(job, res.headers) : this.buyJob(job, { signal: options.signal });
|
|
1885
|
+
}
|
|
1886
|
+
return boughtResult(res.json, res.headers);
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* An async purchase (`buy` with `wait: false`, or one an agent started): waits for its result, long-polling the
|
|
1890
|
+
* Gateway until the seller's job is done (then charged) or its time is up (refunded: throws the error, TIMEOUT).
|
|
1891
|
+
* `wait: false`: one look, `pending: true` while it runs.
|
|
1892
|
+
*/
|
|
1893
|
+
async buyJob(job, options = {}) {
|
|
1894
|
+
for (; ; ) {
|
|
1895
|
+
const res = await this.send("GET", `/v1/pay/jobs/${encodeURIComponent(job)}`, { query: { wait: options.wait === false ? 0 : 25 }, signal: options.signal, timeoutMs: 45e3 });
|
|
1896
|
+
if (res.status !== 202) return boughtResult(res.json, res.headers);
|
|
1897
|
+
if (options.wait === false) return pendingResult(job, res.headers);
|
|
1898
|
+
}
|
|
1834
1899
|
}
|
|
1835
1900
|
// ── utilities (LLMOK, paid in TON from your wallet) ───────────────────────
|
|
1836
1901
|
/**
|
|
@@ -1845,12 +1910,13 @@ var Village = class extends Ambasada {
|
|
|
1845
1910
|
return res.choices[0]?.message.content ?? "";
|
|
1846
1911
|
},
|
|
1847
1912
|
{
|
|
1848
|
-
|
|
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 })
|
|
1849
1915
|
}
|
|
1850
1916
|
);
|
|
1851
1917
|
images = {
|
|
1852
1918
|
/** Generates an image; the URL is re-hosted in the village files store. Default model: nano_banana. */
|
|
1853
|
-
generate: (req) => this.call("POST", "/v1/images", { body: { ...req, model: req.model ?? "nano_banana" } })
|
|
1919
|
+
generate: (req) => this.call("POST", "/v1/images", { body: { ...req, model: req.model ?? "nano_banana" }, timeoutMs: CLIENT_TIMEOUT_MS.image })
|
|
1854
1920
|
};
|
|
1855
1921
|
tools = {
|
|
1856
1922
|
/** The curated LLMOK tool catalog with TON prices. */
|
|
@@ -1863,6 +1929,8 @@ var Village = class extends Ambasada {
|
|
|
1863
1929
|
const res = await this.send("POST", `/v1/tools/${encodeURIComponent(id)}`, {
|
|
1864
1930
|
body: input ?? {},
|
|
1865
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,
|
|
1866
1934
|
headers: { "idempotency-key": options.idempotencyKey ?? randomKey() }
|
|
1867
1935
|
});
|
|
1868
1936
|
const json = res.json;
|
|
@@ -1907,9 +1975,15 @@ var Village = class extends Ambasada {
|
|
|
1907
1975
|
steer: (text) => this.call("POST", "/v1/agent/steer", { body: { text } }),
|
|
1908
1976
|
/**
|
|
1909
1977
|
* Talk to your agent (docs/AGENT-CHAT.md). With `wait` (the default) it resolves with the reply, up to a
|
|
1910
|
-
* minute; the same `idempotencyKey` is the same turn, so a retry never asks twice.
|
|
1978
|
+
* minute; the same `idempotencyKey` is the same turn, so a retry never asks twice. A reply later than that
|
|
1979
|
+
* (`reply: null`, status `pending`/`running`; `queued: "wake"` while the agent's wake runs): `turn(submission_id)`.
|
|
1911
1980
|
*/
|
|
1912
|
-
chat: (text, options = {}) => this.call("POST", "/v1/agent/chat/turn", { body: { text, wait: options.wait ?? true, idempotency_key: options.idempotencyKey ?? randomKey() } }),
|
|
1981
|
+
chat: (text, options = {}) => this.call("POST", "/v1/agent/chat/turn", { body: { text, wait: options.wait ?? true, idempotency_key: options.idempotencyKey ?? randomKey() }, timeoutMs: CHAT_WAIT_MS + 15e3 }),
|
|
1982
|
+
/** A turn sent before and its reply, waiting up to `waitSeconds` (0–60, default 60) for one still running. */
|
|
1983
|
+
turn: (submissionId, options = {}) => {
|
|
1984
|
+
const wait = options.waitSeconds ?? CHAT_WAIT_MS / 1e3;
|
|
1985
|
+
return this.call("GET", `/v1/agent/chat/turn/${encodeURIComponent(submissionId)}`, { query: { wait }, timeoutMs: wait * 1e3 + 15e3 });
|
|
1986
|
+
},
|
|
1913
1987
|
/** The chat, oldest first: your words, the agent's replies and proposals, and cards for what happened. */
|
|
1914
1988
|
messages: async (options = {}) => (await this.call("GET", "/v1/agent/chat/messages", { query: { limit: options.limit ?? 50, ...options.since ? { since: options.since } : {} } })).messages,
|
|
1915
1989
|
/** Proposals waiting for your team. */
|
|
@@ -1923,6 +1997,20 @@ var Village = class extends Ambasada {
|
|
|
1923
1997
|
function createVillage(options = {}) {
|
|
1924
1998
|
return new Village(options);
|
|
1925
1999
|
}
|
|
2000
|
+
function pendingResult(job, headers) {
|
|
2001
|
+
return { output: null, pending: true, job, callId: headers.get("x-village-call-id") ?? job, amount: null, receipt: null, logIndex: null, replayed: false, offering: null };
|
|
2002
|
+
}
|
|
2003
|
+
function boughtResult(json, h) {
|
|
2004
|
+
return {
|
|
2005
|
+
output: json,
|
|
2006
|
+
callId: h.get("x-village-call-id"),
|
|
2007
|
+
amount: num(h.get("x-village-amount")),
|
|
2008
|
+
receipt: h.get("x-village-receipt"),
|
|
2009
|
+
logIndex: num(h.get("x-village-log-index")),
|
|
2010
|
+
replayed: h.get("idempotent-replayed") === "true",
|
|
2011
|
+
offering: h.get("x-village-offering")
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
1926
2014
|
|
|
1927
2015
|
// src/define.ts
|
|
1928
2016
|
function defineCompany(app, village2) {
|
|
@@ -1935,6 +2023,8 @@ function offering(def) {
|
|
|
1935
2023
|
if (!(def.price >= 0.1 && def.price <= 500)) throw new Error(`offering ${def.name}: price must be between 0.1 and 500 TON`);
|
|
1936
2024
|
const timeout = offeringTimeoutProblem(def.timeout_ms);
|
|
1937
2025
|
if (timeout) throw new Error(`offering ${def.name}: ${timeout}`);
|
|
2026
|
+
const later = asyncOfferingProblem(def.async, def.max_wait_s);
|
|
2027
|
+
if (later) throw new Error(`offering ${def.name}: ${later}`);
|
|
1938
2028
|
return def;
|
|
1939
2029
|
}
|
|
1940
2030
|
|
|
@@ -1951,7 +2041,7 @@ export {
|
|
|
1951
2041
|
RECEIPT_HEADER2 as RECEIPT_HEADER,
|
|
1952
2042
|
ReceiptInvalidError,
|
|
1953
2043
|
Village,
|
|
1954
|
-
VillageError,
|
|
2044
|
+
VillageError2 as VillageError,
|
|
1955
2045
|
WEBHOOK_HEADERS2 as WEBHOOK_HEADERS,
|
|
1956
2046
|
WebhookInvalidError2 as WebhookInvalidError,
|
|
1957
2047
|
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
|
-
/**
|
|
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 {
|
|
@@ -138,7 +149,22 @@ export interface BuyOptions {
|
|
|
138
149
|
/** Why you are buying; shown to your team on approval requests. */
|
|
139
150
|
reason?: string;
|
|
140
151
|
signal?: AbortSignal;
|
|
141
|
-
|
|
152
|
+
/**
|
|
153
|
+
* An async offering answers with a job: by default `buy` waits for its result (long-polling the Gateway, up to the
|
|
154
|
+
* offering's `max_wait_s`). `wait: false` returns at once with `pending: true` and the `job` (`village.buyJob(job)` later).
|
|
155
|
+
*/
|
|
156
|
+
wait?: boolean;
|
|
157
|
+
}
|
|
158
|
+
/** What an async offering's `GET <endpoint>/jobs/<id>` says (village.jobStatus): the Gateway settles on done or failed. */
|
|
159
|
+
export type JobAnswer = {
|
|
160
|
+
status: "working";
|
|
161
|
+
} | {
|
|
162
|
+
status: "done";
|
|
163
|
+
output: unknown;
|
|
164
|
+
} | {
|
|
165
|
+
status: "failed";
|
|
166
|
+
error: string;
|
|
167
|
+
};
|
|
142
168
|
export interface BuyResult<Output = any> {
|
|
143
169
|
output: Output;
|
|
144
170
|
callId: string | null;
|
|
@@ -152,6 +178,10 @@ export interface BuyResult<Output = any> {
|
|
|
152
178
|
replayed: boolean;
|
|
153
179
|
/** For `need()`: which offering served it (`company/name`), if the Gateway says. */
|
|
154
180
|
offering: string | null;
|
|
181
|
+
/** An async offering's job still running (`wait: false`): no output yet and nothing charged. */
|
|
182
|
+
pending?: boolean;
|
|
183
|
+
/** The async purchase's job (also its call id): `village.buyJob(job)` reads it. */
|
|
184
|
+
job?: string;
|
|
155
185
|
}
|
|
156
186
|
/** What the village sells (apps/api/config/pricing.json); any other model is refused. */
|
|
157
187
|
export type ChatModel = "deepseek-v4.1-flash" | (string & {});
|
|
@@ -166,6 +196,8 @@ export interface ChatRequest {
|
|
|
166
196
|
messages: ChatMessage[];
|
|
167
197
|
temperature?: number;
|
|
168
198
|
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 & {});
|
|
169
201
|
/** `json_schema`: JSON in the schema's shape (live, and the mock's sample of it under `ambasada dev`); `json_object`: any JSON. */
|
|
170
202
|
response_format?: {
|
|
171
203
|
type: "text" | "json_object";
|
|
@@ -237,6 +269,10 @@ export interface ListingInput {
|
|
|
237
269
|
timeout_ms?: number;
|
|
238
270
|
status?: "open" | "paused";
|
|
239
271
|
preview?: boolean;
|
|
272
|
+
/** The work takes longer than a request: answer `{ job }` and serve `GET <endpoint>/jobs/<job>` (village.jobStatus). */
|
|
273
|
+
async?: boolean;
|
|
274
|
+
/** Async: how long the Gateway waits for the job, seconds (10–600, default 300); then the buyer is refunded. */
|
|
275
|
+
max_wait_s?: number;
|
|
240
276
|
}
|
|
241
277
|
export interface Listing extends ListingInput {
|
|
242
278
|
id: string;
|
|
@@ -358,6 +394,8 @@ export interface ChatTurn {
|
|
|
358
394
|
/** With `wait`: how the turn ended and the agent's reply. */
|
|
359
395
|
status?: "completed" | "error" | "aborted" | "skipped" | "pending" | "running";
|
|
360
396
|
error?: string;
|
|
397
|
+
/** Still waiting: "wake" while the agent's wake runs (the turn is answered after it). */
|
|
398
|
+
queued?: "wake";
|
|
361
399
|
reply?: ChatLine | null;
|
|
362
400
|
}
|
|
363
401
|
export interface MarketOffering {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ambasada/village",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
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.8"
|
|
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.8"
|
|
39
39
|
},
|
|
40
40
|
"author": "CENTYSO (https://centyso.com)",
|
|
41
41
|
"homepage": "https://ambasada.id",
|