@onlyworlds/sdk 4.11.0 → 4.13.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/AGENTS.md +6 -0
- package/CHANGELOG.md +32 -0
- package/README.md +21 -1
- package/dist/index.d.ts +129 -1
- package/dist/index.js +102 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -40,6 +40,12 @@ against live data). The v1 surface (`OnlyWorldsClient`, the `ElementType` enum)
|
|
|
40
40
|
- Images: `uploadImage(bytes)` returns `{ url, ... }`; set `url` as `image_url`. The bytes go
|
|
41
41
|
to the edge with a single-use ticket from keel, never with the API key. `removeImage(urlOrKey)`
|
|
42
42
|
takes one back (its uploader or the world's owner); it leaves `image_url` on elements as is.
|
|
43
|
+
- Ask (since 4.13.0, account token): `buildAskContext(hits, index)` then
|
|
44
|
+
`account.ask({ worldId, question, context })`; answers only from the context. 50 a day per
|
|
45
|
+
account (`err.isQuotaExceeded`); `isAskUnavailable`, `isAskFailed` count nothing.
|
|
46
|
+
- Invite links (since 4.12.0): `previewJoin(code)` (no credential sent) shows who invites, to
|
|
47
|
+
which world, in what role; `acceptJoin(code)` with an `ow_a_` account token joins. Bad code:
|
|
48
|
+
`err.isNotFound`; `err.isAlreadyMember`, `err.isPinRequired` for the two 409s.
|
|
43
49
|
- Lookup without a model (since 4.11.0): `buildWorldIndex(elements)` then
|
|
44
50
|
`lookupWorld(index, question)` ranks the elements a question is about (names, then kind, one
|
|
45
51
|
link step, then text); `renderElement(el, index)` is a compact card with links by name.
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,38 @@ earlier history lives in git log only.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [4.13.0] — 2026-10-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Ask your world (keel #159).** `client.ask({ worldId, question, context })` sends a
|
|
12
|
+
question to keel's AI proxy with the account's `ow_a_` token (a world key is a `TypeError`
|
|
13
|
+
before sending) and returns `OwAskAnswer` (`answer`, `model`, `stop_reason`, `usage`,
|
|
14
|
+
`remaining`). The answer comes from `context` only, or is "The world doesn't say.".
|
|
15
|
+
`buildAskContext(items, index)` turns the elements the user sees (lookup hits or plain
|
|
16
|
+
elements, in the caller's order, never re-looked-up) into that `context` within keel's
|
|
17
|
+
limits (`ASK_LIMITS`: 8 items, 2,000 characters each, 12,000 in all, counted in characters
|
|
18
|
+
as keel counts them) and says what it did: `kept`, `dropped` (past the 8th, or from the first
|
|
19
|
+
one the total cannot hold, so what is sent is always the top of the list) and `truncated`.
|
|
20
|
+
New getters: `isQuotaExceeded` (429; `retryAfter` runs to midnight UTC), `isAskUnavailable`
|
|
21
|
+
(503), `isAskFailed` (502). keel's document gives no request schema for `/account/ask`, so
|
|
22
|
+
the body (`world_id`, `question`, `context`) is read from keel's code (`accounts/ask.py`).
|
|
23
|
+
Not yet run live: no crew session holds an account token.
|
|
24
|
+
|
|
25
|
+
## [4.12.0] — 2026-10-12
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **Joining a world by link (keel #212).** `previewJoin(code)` reads what a join code holds
|
|
29
|
+
without using it (`OwJoinPreview`: `reader`, `world`, `invited_by`, `owner_character`,
|
|
30
|
+
`role`, `with_character`, `expires_at`) and sends no credential, whatever key the client
|
|
31
|
+
holds. `acceptJoin(code)` accepts an invite link as the account behind the client's `ow_a_`
|
|
32
|
+
token and returns the new membership (`OwJoinedMembership`, `roster_character` included);
|
|
33
|
+
a world key is a `TypeError` before anything is sent. New getters on `OwApiError`:
|
|
34
|
+
`isNotFound` (every bad code is one 404), `isAlreadyMember` and `isPinRequired` (the two
|
|
35
|
+
409s, told apart by code). Shapes read from keel's code (`accounts/join_api.py`,
|
|
36
|
+
`accounts/api.py`), since the OpenAPI document gives no response bodies for these routes.
|
|
37
|
+
Checked live: a bad code on production answers `isNotFound`. A real accept has not been
|
|
38
|
+
run from here (no account token); Lantern's door is its first live use.
|
|
39
|
+
|
|
8
40
|
## [4.11.0] — 2026-10-12
|
|
9
41
|
|
|
10
42
|
### Added
|
package/README.md
CHANGED
|
@@ -110,6 +110,17 @@ await writer.patch('character', id, { image_url: null });
|
|
|
110
110
|
|
|
111
111
|
The image's uploader or the world's owner can remove an uploaded image; the space goes back to the uploader's storage. Removal leaves elements as they are, so clear their `image_url` yourself (`createRemovalTicket(key).referenced` says how many still show it). A copy the network already holds can keep answering for a while.
|
|
112
112
|
|
|
113
|
+
## Invite links
|
|
114
|
+
|
|
115
|
+
A world's owner can invite a person with a link (`https://www.onlyworlds.com/join#ow_j_…`). The code is the part after `#`:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const invite = await client.previewJoin(code); // who invites, which world, what role; sends no key
|
|
119
|
+
const membership = await account.acceptJoin(code); // account = a client with an ow_a_ token
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
A bad code (unknown, used, revoked, expired) is one 404 (`err.isNotFound`). Accepting can answer `err.isAlreadyMember` or `err.isPinRequired` (set a PIN first); a refusal never uses up the code.
|
|
123
|
+
|
|
113
124
|
## Sync
|
|
114
125
|
|
|
115
126
|
```typescript
|
|
@@ -150,7 +161,16 @@ hits[0].element; hits[0].reasons; // best first, with why each m
|
|
|
150
161
|
renderElement(hits[0].element, index); // a short text card, links by name
|
|
151
162
|
```
|
|
152
163
|
|
|
153
|
-
Names come first, then supertype and subtype, then one link step in either direction, then the elements' other text.
|
|
164
|
+
Names come first, then supertype and subtype, then one link step in either direction, then the elements' other text.
|
|
165
|
+
|
|
166
|
+
To have an answer written from what you found, OnlyWorlds runs a small AI proxy for signed-in accounts (50 questions a day):
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const shown = hits.slice(0, 8); // what your screen lists
|
|
170
|
+
const { context, kept } = buildAskContext(shown, index); // within the proxy's limits; kept = what was sent
|
|
171
|
+
const reply = await account.ask({ worldId, question, context }); // account: a client with an ow_a_ token
|
|
172
|
+
reply.answer; // from those elements only, or "The world doesn't say."
|
|
173
|
+
```
|
|
154
174
|
|
|
155
175
|
## World files
|
|
156
176
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1988,6 +1988,51 @@ interface OwRemovalTicket {
|
|
|
1988
1988
|
referenced: number;
|
|
1989
1989
|
}
|
|
1990
1990
|
/** The edge's 200 for one removal. */
|
|
1991
|
+
/** POST /account/ask: keel's AI proxy's answer (keel #159). */
|
|
1992
|
+
interface OwAskAnswer {
|
|
1993
|
+
/** The answer, from the context only; "The world doesn't say." when it does not hold one. */
|
|
1994
|
+
answer: string;
|
|
1995
|
+
model: string;
|
|
1996
|
+
stop_reason: string | null;
|
|
1997
|
+
usage: {
|
|
1998
|
+
input_tokens: number;
|
|
1999
|
+
output_tokens: number;
|
|
2000
|
+
};
|
|
2001
|
+
/** Questions the account has left today. */
|
|
2002
|
+
remaining: number;
|
|
2003
|
+
[k: string]: unknown;
|
|
2004
|
+
}
|
|
2005
|
+
/** POST /join/preview: what a join code holds, read without using it (keel #212). */
|
|
2006
|
+
interface OwJoinPreview {
|
|
2007
|
+
/** `person`: an invite link, accepted with acceptJoin. `agent`: redeemed by an AI agent at POST /join. */
|
|
2008
|
+
reader: 'person' | 'agent' | (string & {});
|
|
2009
|
+
world: {
|
|
2010
|
+
name: string;
|
|
2011
|
+
description: string | null;
|
|
2012
|
+
};
|
|
2013
|
+
/** The name the inviter signed the link with, or null. */
|
|
2014
|
+
invited_by: string | null;
|
|
2015
|
+
/** The world owner's chosen Character's name, never a username; null when none is set. */
|
|
2016
|
+
owner_character: string | null;
|
|
2017
|
+
role: 'co-builder' | 'contributor' | 'guest' | (string & {});
|
|
2018
|
+
/** Whether joining gives the joiner a Character of their own (agent links). */
|
|
2019
|
+
with_character: boolean | null;
|
|
2020
|
+
/** ISO 8601, UTC. */
|
|
2021
|
+
expires_at: string;
|
|
2022
|
+
[k: string]: unknown;
|
|
2023
|
+
}
|
|
2024
|
+
/** POST /account/join: the new membership (the same row as accepting an email invite). */
|
|
2025
|
+
interface OwJoinedMembership {
|
|
2026
|
+
/** The membership's id. */
|
|
2027
|
+
id: string;
|
|
2028
|
+
world_id: string;
|
|
2029
|
+
world_name: string;
|
|
2030
|
+
role: 'co-builder' | 'contributor' | 'guest' | (string & {});
|
|
2031
|
+
joined_at: string | null;
|
|
2032
|
+
/** The id of the joiner's roster Character, or null. */
|
|
2033
|
+
roster_character: string | null;
|
|
2034
|
+
[k: string]: unknown;
|
|
2035
|
+
}
|
|
1991
2036
|
interface OwRemovedImage {
|
|
1992
2037
|
/** The object key that was removed. */
|
|
1993
2038
|
removed: string;
|
|
@@ -2139,6 +2184,18 @@ declare class OwApiError extends Error {
|
|
|
2139
2184
|
* is now, so no second read is needed.
|
|
2140
2185
|
*/
|
|
2141
2186
|
get isPreconditionFailed(): boolean;
|
|
2187
|
+
/** 404 `not_found`: no such element or route, or a join code that is unknown, used, revoked or expired. */
|
|
2188
|
+
get isNotFound(): boolean;
|
|
2189
|
+
/** acceptJoin: the account is already a member of that world (409 `already_member`). The code is not spent. */
|
|
2190
|
+
get isAlreadyMember(): boolean;
|
|
2191
|
+
/** acceptJoin: the account has no PIN yet, and a member writes with their own (409 `pin_required`). The code is not spent. */
|
|
2192
|
+
get isPinRequired(): boolean;
|
|
2193
|
+
/** A daily allowance is used up (429 `quota_exceeded`): the world's image tickets, or the account's asks. See retryAfter. */
|
|
2194
|
+
get isQuotaExceeded(): boolean;
|
|
2195
|
+
/** ask: the proxy is switched off, or paused for the day by the server-wide cap (503 `ask_unavailable`). Not counted. */
|
|
2196
|
+
get isAskUnavailable(): boolean;
|
|
2197
|
+
/** ask: the model gave no answer (502 `ask_failed`). Not counted; try again in a moment. */
|
|
2198
|
+
get isAskFailed(): boolean;
|
|
2142
2199
|
}
|
|
2143
2200
|
/**
|
|
2144
2201
|
* 412 `precondition_failed` (keel #210): the element changed since the `change_seq` sent as
|
|
@@ -2368,6 +2425,38 @@ declare class OwV2Client {
|
|
|
2368
2425
|
removeImage(keyOrUrl: string, opts?: {
|
|
2369
2426
|
ticket?: OwRemovalTicket;
|
|
2370
2427
|
}): Promise<OwRemovedImage>;
|
|
2428
|
+
/**
|
|
2429
|
+
* POST /join/preview -- what a join code holds, without using it: who invites, to
|
|
2430
|
+
* which world, in what role, and `reader` (`person`: accept it with acceptJoin;
|
|
2431
|
+
* `agent`: an AI agent redeems it at POST /join). Sends no credential, whatever key
|
|
2432
|
+
* this client holds. Every bad code (unknown, used, revoked, expired) is the same
|
|
2433
|
+
* 404 `not_found` (`err.isNotFound`). `code` is the part of the link after `#`.
|
|
2434
|
+
*/
|
|
2435
|
+
previewJoin(code: string): Promise<OwJoinPreview>;
|
|
2436
|
+
/**
|
|
2437
|
+
* POST /account/join -- accept an invite link's code as the account this client's
|
|
2438
|
+
* `ow_a_` token belongs to: 201 with the new membership. Refusals: 404 `not_found`
|
|
2439
|
+
* (every bad code, and a code made for an AI agent), 409 `already_member`
|
|
2440
|
+
* (`err.isAlreadyMember`) and 409 `pin_required` (`err.isPinRequired`: the account
|
|
2441
|
+
* has no PIN yet). A refusal never spends the code. A world key here is a TypeError
|
|
2442
|
+
* before anything is sent.
|
|
2443
|
+
*/
|
|
2444
|
+
acceptJoin(code: string): Promise<OwJoinedMembership>;
|
|
2445
|
+
/**
|
|
2446
|
+
* POST /account/ask -- a question about a world the account owns or is in, answered by
|
|
2447
|
+
* keel's AI proxy from `context` only (build it with buildAskContext from the elements
|
|
2448
|
+
* the user sees). The answer is "The world doesn't say." when the context does not hold
|
|
2449
|
+
* it. Needs the client's `ow_a_` token (a world key is a TypeError before sending).
|
|
2450
|
+
* Refusals: 429 `quota_exceeded` (`err.isQuotaExceeded`, the account's daily allowance;
|
|
2451
|
+
* Retry-After runs to midnight UTC), 503 `ask_unavailable` (`err.isAskUnavailable`:
|
|
2452
|
+
* switched off, or paused for the day), 502 `ask_failed` (`err.isAskFailed`); none of
|
|
2453
|
+
* them counts. 422 for a question over 500 characters or context past its limits.
|
|
2454
|
+
*/
|
|
2455
|
+
ask(req: {
|
|
2456
|
+
worldId: string;
|
|
2457
|
+
question: string;
|
|
2458
|
+
context: string[];
|
|
2459
|
+
}): Promise<OwAskAnswer>;
|
|
2371
2460
|
/**
|
|
2372
2461
|
* Raw authenticated request against this client's baseUrl. Public since 4.0
|
|
2373
2462
|
* so auxiliary resources can ride the same transport — it structurally
|
|
@@ -2660,6 +2749,45 @@ declare function lookupWorld<T extends OwLookupElement>(index: OwWorldIndex<T>,
|
|
|
2660
2749
|
*/
|
|
2661
2750
|
declare function renderElement<T extends OwLookupElement>(element: T, index: OwWorldIndex<T>, { descMax, linkMax, incomingMax }?: OwRenderOptions): string;
|
|
2662
2751
|
|
|
2752
|
+
/**
|
|
2753
|
+
* Context for keel's AI proxy (POST /api/v2/account/ask, Keel #159): the elements a
|
|
2754
|
+
* question is about, as text, within the proxy's limits. The caller passes the
|
|
2755
|
+
* elements it shows the user, in its own order, and gets back exactly which were sent:
|
|
2756
|
+
* the answer's sources are what the user saw, never a second lookup's.
|
|
2757
|
+
*
|
|
2758
|
+
* const hits = lookupWorld(index, question).slice(0, 8); // what the screen lists
|
|
2759
|
+
* const { context, kept } = buildAskContext(hits, index);
|
|
2760
|
+
* const reply = await account.ask({ worldId, question, context });
|
|
2761
|
+
*
|
|
2762
|
+
* The limits are keel's (accounts/ask.py), counted the way keel counts (characters,
|
|
2763
|
+
* not UTF-16 units): at most 8 items, 2,000 characters each, 12,000 in all.
|
|
2764
|
+
*/
|
|
2765
|
+
|
|
2766
|
+
/** keel's limits on one ask. */
|
|
2767
|
+
declare const ASK_LIMITS: {
|
|
2768
|
+
readonly questionMax: 500;
|
|
2769
|
+
readonly itemsMax: 8;
|
|
2770
|
+
readonly itemMax: 2000;
|
|
2771
|
+
readonly totalMax: 12000;
|
|
2772
|
+
};
|
|
2773
|
+
interface OwAskContext {
|
|
2774
|
+
/** The texts to send as `context`, in the order given. */
|
|
2775
|
+
context: string[];
|
|
2776
|
+
/** Ids of the elements sent, in order (one per `context` item). */
|
|
2777
|
+
kept: string[];
|
|
2778
|
+
/** Ids of the elements left out: past the 8th, or from the first one the 12,000 total could not hold. */
|
|
2779
|
+
dropped: string[];
|
|
2780
|
+
/** Ids among `kept` whose text was cut to 2,000 characters. */
|
|
2781
|
+
truncated: string[];
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Turn the elements on screen into the proxy's `context`. Takes lookup hits or plain
|
|
2785
|
+
* elements, keeps their order, renders each with `renderElement`, cuts a text over
|
|
2786
|
+
* 2,000 characters, and stops at the 8th or at the first one the 12,000 total cannot
|
|
2787
|
+
* hold, so what is sent is always the top of the list as shown.
|
|
2788
|
+
*/
|
|
2789
|
+
declare function buildAskContext<T extends OwLookupElement>(items: Array<OwLookupHit<T> | T>, index: OwWorldIndex<T>): OwAskContext;
|
|
2790
|
+
|
|
2663
2791
|
/**
|
|
2664
2792
|
* Photo preparation: make a picture from a phone's camera or files ready for
|
|
2665
2793
|
* `uploadImage` (webp, png, jpeg or avif, at most 15 MB), in the browser,
|
|
@@ -3009,4 +3137,4 @@ declare class TokenResource {
|
|
|
3009
3137
|
getEncryptionInfo(): Promise<EncryptionInfo>;
|
|
3010
3138
|
}
|
|
3011
3139
|
|
|
3012
|
-
export { type AccessKeyResponse, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, FAMILY_COLORS, FAMILY_ORDER, FIELD_SCHEMA, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, OwApiError, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwChange, type OwChangesPage, type OwClientConfig, type OwElement, type OwElementBase, type OwErrorBody, type OwKeyKind, type OwLinkEdit, type OwLookupElement, type OwLookupHit, type OwLookupLink, type OwLookupOptions, type OwLookupReason, type OwMediaTicket, OwNetworkError, type OwPage, type OwPhotoOptions, type OwPhotoPlan, OwPreconditionFailedError, type OwPreparedPhoto, type OwReadWorldFile, type OwReadWorldFileOptions, type OwRemovalTicket, type OwRemovedImage, type OwRenderOptions, type OwUploadedImage, OwV2Client, type OwWorldFileEntry, OwWorldFileError, type OwWorldFileReason, type OwWorldIndex, type OwWorldMeta, PHOTO_MAX_BYTES, PHOTO_TYPES, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, WORLD_FILE_MAX_BYTES, WORLD_FILE_MAX_ENTRIES, buildWorldIndex, detectKeyKind, elementColor, errorFromResponse, familyOf, getElementIcon, getElementLabel, isDemoKey, kindCanWrite, lookupWorld, parseErrorEnvelope, pinExpectation, planPhoto, preparePhoto, readWorldFile, renderElement, writeWorldFile };
|
|
3140
|
+
export { ASK_LIMITS, type AccessKeyResponse, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, FAMILY_COLORS, FAMILY_ORDER, FIELD_SCHEMA, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, OwApiError, type OwAskAnswer, type OwAskContext, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwChange, type OwChangesPage, type OwClientConfig, type OwElement, type OwElementBase, type OwErrorBody, type OwJoinPreview, type OwJoinedMembership, type OwKeyKind, type OwLinkEdit, type OwLookupElement, type OwLookupHit, type OwLookupLink, type OwLookupOptions, type OwLookupReason, type OwMediaTicket, OwNetworkError, type OwPage, type OwPhotoOptions, type OwPhotoPlan, OwPreconditionFailedError, type OwPreparedPhoto, type OwReadWorldFile, type OwReadWorldFileOptions, type OwRemovalTicket, type OwRemovedImage, type OwRenderOptions, type OwUploadedImage, OwV2Client, type OwWorldFileEntry, OwWorldFileError, type OwWorldFileReason, type OwWorldIndex, type OwWorldMeta, PHOTO_MAX_BYTES, PHOTO_TYPES, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, WORLD_FILE_MAX_BYTES, WORLD_FILE_MAX_ENTRIES, buildAskContext, buildWorldIndex, detectKeyKind, elementColor, errorFromResponse, familyOf, getElementIcon, getElementLabel, isDemoKey, kindCanWrite, lookupWorld, parseErrorEnvelope, pinExpectation, planPhoto, preparePhoto, readWorldFile, renderElement, writeWorldFile };
|
package/dist/index.js
CHANGED
|
@@ -75,6 +75,30 @@ var OwApiError = class extends Error {
|
|
|
75
75
|
get isPreconditionFailed() {
|
|
76
76
|
return this.status === 412 && this.code === "precondition_failed";
|
|
77
77
|
}
|
|
78
|
+
/** 404 `not_found`: no such element or route, or a join code that is unknown, used, revoked or expired. */
|
|
79
|
+
get isNotFound() {
|
|
80
|
+
return this.status === 404 && this.code === "not_found";
|
|
81
|
+
}
|
|
82
|
+
/** acceptJoin: the account is already a member of that world (409 `already_member`). The code is not spent. */
|
|
83
|
+
get isAlreadyMember() {
|
|
84
|
+
return this.status === 409 && this.code === "already_member";
|
|
85
|
+
}
|
|
86
|
+
/** acceptJoin: the account has no PIN yet, and a member writes with their own (409 `pin_required`). The code is not spent. */
|
|
87
|
+
get isPinRequired() {
|
|
88
|
+
return this.status === 409 && this.code === "pin_required";
|
|
89
|
+
}
|
|
90
|
+
/** A daily allowance is used up (429 `quota_exceeded`): the world's image tickets, or the account's asks. See retryAfter. */
|
|
91
|
+
get isQuotaExceeded() {
|
|
92
|
+
return this.status === 429 && this.code === "quota_exceeded";
|
|
93
|
+
}
|
|
94
|
+
/** ask: the proxy is switched off, or paused for the day by the server-wide cap (503 `ask_unavailable`). Not counted. */
|
|
95
|
+
get isAskUnavailable() {
|
|
96
|
+
return this.status === 503 && this.code === "ask_unavailable";
|
|
97
|
+
}
|
|
98
|
+
/** ask: the model gave no answer (502 `ask_failed`). Not counted; try again in a moment. */
|
|
99
|
+
get isAskFailed() {
|
|
100
|
+
return this.status === 502 && this.code === "ask_failed";
|
|
101
|
+
}
|
|
78
102
|
};
|
|
79
103
|
var OwPreconditionFailedError = class extends OwApiError {
|
|
80
104
|
constructor(status, code, message, docUrl, detail, type = null, param = null, retryAfter = null) {
|
|
@@ -410,6 +434,50 @@ var OwV2Client = class {
|
|
|
410
434
|
if (!res.ok) throw await errorFromResponse(res);
|
|
411
435
|
return await res.json();
|
|
412
436
|
}
|
|
437
|
+
// -- Joining a world by link (keel #212) ----------------------------------
|
|
438
|
+
/**
|
|
439
|
+
* POST /join/preview -- what a join code holds, without using it: who invites, to
|
|
440
|
+
* which world, in what role, and `reader` (`person`: accept it with acceptJoin;
|
|
441
|
+
* `agent`: an AI agent redeems it at POST /join). Sends no credential, whatever key
|
|
442
|
+
* this client holds. Every bad code (unknown, used, revoked, expired) is the same
|
|
443
|
+
* 404 `not_found` (`err.isNotFound`). `code` is the part of the link after `#`.
|
|
444
|
+
*/
|
|
445
|
+
async previewJoin(code) {
|
|
446
|
+
return this.request("POST", "/join/preview", { auth: false, body: { code } });
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* POST /account/join -- accept an invite link's code as the account this client's
|
|
450
|
+
* `ow_a_` token belongs to: 201 with the new membership. Refusals: 404 `not_found`
|
|
451
|
+
* (every bad code, and a code made for an AI agent), 409 `already_member`
|
|
452
|
+
* (`err.isAlreadyMember`) and 409 `pin_required` (`err.isPinRequired`: the account
|
|
453
|
+
* has no PIN yet). A refusal never spends the code. A world key here is a TypeError
|
|
454
|
+
* before anything is sent.
|
|
455
|
+
*/
|
|
456
|
+
async acceptJoin(code) {
|
|
457
|
+
if (this.keyKind !== "account") {
|
|
458
|
+
throw new TypeError("acceptJoin needs an account token (ow_a_): a world key cannot join a world");
|
|
459
|
+
}
|
|
460
|
+
return this.request("POST", "/account/join", { body: { code } });
|
|
461
|
+
}
|
|
462
|
+
// -- Ask your world (keel #159) -------------------------------------------
|
|
463
|
+
/**
|
|
464
|
+
* POST /account/ask -- a question about a world the account owns or is in, answered by
|
|
465
|
+
* keel's AI proxy from `context` only (build it with buildAskContext from the elements
|
|
466
|
+
* the user sees). The answer is "The world doesn't say." when the context does not hold
|
|
467
|
+
* it. Needs the client's `ow_a_` token (a world key is a TypeError before sending).
|
|
468
|
+
* Refusals: 429 `quota_exceeded` (`err.isQuotaExceeded`, the account's daily allowance;
|
|
469
|
+
* Retry-After runs to midnight UTC), 503 `ask_unavailable` (`err.isAskUnavailable`:
|
|
470
|
+
* switched off, or paused for the day), 502 `ask_failed` (`err.isAskFailed`); none of
|
|
471
|
+
* them counts. 422 for a question over 500 characters or context past its limits.
|
|
472
|
+
*/
|
|
473
|
+
async ask(req) {
|
|
474
|
+
if (this.keyKind !== "account") {
|
|
475
|
+
throw new TypeError("ask needs an account token (ow_a_): the proxy meters per account");
|
|
476
|
+
}
|
|
477
|
+
return this.request("POST", "/account/ask", {
|
|
478
|
+
body: { world_id: req.worldId, question: req.question, context: req.context }
|
|
479
|
+
});
|
|
480
|
+
}
|
|
413
481
|
// -- Core request machinery ----------------------------------------------
|
|
414
482
|
/**
|
|
415
483
|
* Raw authenticated request against this client's baseUrl. Public since 4.0
|
|
@@ -1834,6 +1902,38 @@ function renderElement(element, index, { descMax = 500, linkMax = 12, incomingMa
|
|
|
1834
1902
|
return lines.join("\n");
|
|
1835
1903
|
}
|
|
1836
1904
|
|
|
1905
|
+
// src/ask.ts
|
|
1906
|
+
var ASK_LIMITS = { questionMax: 500, itemsMax: 8, itemMax: 2e3, totalMax: 12e3 };
|
|
1907
|
+
var chars = (s) => [...s].length;
|
|
1908
|
+
var cut = (s, n) => [...s].slice(0, n).join("");
|
|
1909
|
+
function buildAskContext(items, index) {
|
|
1910
|
+
const out = { context: [], kept: [], dropped: [], truncated: [] };
|
|
1911
|
+
let total = 0;
|
|
1912
|
+
let full = false;
|
|
1913
|
+
for (const item of items) {
|
|
1914
|
+
const el = item && typeof item === "object" && "element" in item ? item.element : item;
|
|
1915
|
+
if (full || out.context.length >= ASK_LIMITS.itemsMax) {
|
|
1916
|
+
out.dropped.push(el.id);
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
let text = renderElement(el, index);
|
|
1920
|
+
if (chars(text) > ASK_LIMITS.itemMax) {
|
|
1921
|
+
text = cut(text, ASK_LIMITS.itemMax);
|
|
1922
|
+
out.truncated.push(el.id);
|
|
1923
|
+
}
|
|
1924
|
+
if (total + chars(text) > ASK_LIMITS.totalMax) {
|
|
1925
|
+
full = true;
|
|
1926
|
+
out.truncated = out.truncated.filter((id) => id !== el.id);
|
|
1927
|
+
out.dropped.push(el.id);
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
total += chars(text);
|
|
1931
|
+
out.context.push(text);
|
|
1932
|
+
out.kept.push(el.id);
|
|
1933
|
+
}
|
|
1934
|
+
return out;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1837
1937
|
// src/photo.ts
|
|
1838
1938
|
var PHOTO_TYPES = ["image/webp", "image/png", "image/jpeg", "image/avif"];
|
|
1839
1939
|
var PHOTO_MAX_BYTES = 15 * 1024 * 1024;
|
|
@@ -2068,6 +2168,7 @@ var GameTier = /* @__PURE__ */ ((GameTier2) => {
|
|
|
2068
2168
|
return GameTier2;
|
|
2069
2169
|
})(GameTier || {});
|
|
2070
2170
|
export {
|
|
2171
|
+
ASK_LIMITS,
|
|
2071
2172
|
ELEMENT_FAMILIES,
|
|
2072
2173
|
ELEMENT_ICONS,
|
|
2073
2174
|
ELEMENT_LABELS,
|
|
@@ -2089,6 +2190,7 @@ export {
|
|
|
2089
2190
|
TokenResource,
|
|
2090
2191
|
WORLD_FILE_MAX_BYTES,
|
|
2091
2192
|
WORLD_FILE_MAX_ENTRIES,
|
|
2193
|
+
buildAskContext,
|
|
2092
2194
|
buildWorldIndex,
|
|
2093
2195
|
detectKeyKind,
|
|
2094
2196
|
elementColor,
|