palbase 0.3.2 → 0.4.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/dist/ai/index.js +10 -13
- package/dist/backend/engine.js +2 -2
- package/dist/backend/env.js +1 -1
- package/dist/backend/index.js +9 -9
- package/dist/backend/stack.js +1 -1
- package/dist/backend/test.js +2 -2
- package/dist/chunks/{HHSKJTI6.js → 2AT72FFB.js} +2 -2
- package/dist/chunks/{TTKDWRP5.js → 3OIKIBMG.js} +2 -2
- package/dist/chunks/{JFWH2INT.js → 6JO3UVPT.js} +2 -2
- package/dist/chunks/{XUG2RG4F.js → BHT2IHBE.js} +2 -2
- package/dist/chunks/BJL2FT5W.js +2 -0
- package/dist/chunks/CY6B75KF.js +6 -0
- package/dist/chunks/EGRDFD5C.js +20 -0
- package/dist/chunks/{RHQ2NBCI.js → FS222NNP.js} +2 -2
- package/dist/chunks/{ZEFNZ6DZ.js → IVDNNHII.js} +2 -2
- package/dist/chunks/{YSZ2RSTS.js → ODFLUINN.js} +1 -1
- package/dist/chunks/{HJVB6KIR.js → RKNVSAGY.js} +1 -1
- package/dist/chunks/ST2NZ4TZ.js +4 -0
- package/dist/chunks/{33DZIPGE.js → SUNHFMDO.js} +2 -2
- package/dist/chunks/{44SLUFBO.js → TRHIZOIS.js} +3 -3
- package/dist/chunks/{32BPNFZX.js → UNTTPATW.js} +1 -1
- package/dist/chunks/{CNRFJ7AK.js → ZFX4BFQR.js} +2 -2
- package/dist/cli.js +131 -128
- package/dist/refuse/ai.js +1 -1
- package/dist/refuse/backend.js +1 -1
- package/dist/refuse/server.js +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/testing.js +2 -2
- package/dist/web/index.js +2 -2
- package/dist/web/palbe.js +2 -2
- package/dist/web/paywall.js +2 -2
- package/dist/web/vite.js +2 -2
- package/package.json +2 -2
- package/types/ai/src/ai.d.ts +63 -2
- package/types/ai/src/chats.d.ts +147 -0
- package/types/ai/src/index.d.ts +6 -1
- package/types/ai/src/tools.d.ts +167 -0
- package/types/backend/src/engine.d.ts +12 -1
- package/types/backend/src/i18n.d.ts +8 -7
- package/types/backend/src/strings-files.d.ts +28 -1
- package/types/internal/src/chat.d.ts +117 -0
- package/types/internal/src/tool-output.d.ts +20 -0
- package/types/web/src/ai.d.ts +201 -0
- package/types/web/src/client.d.ts +3 -0
- package/types/web/src/index.d.ts +1 -0
- package/dist/chunks/QA3GIZMI.js +0 -4
- package/dist/chunks/QD5XLRIJ.js +0 -2
- package/dist/chunks/QRWBXKEM.js +0 -20
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { ai as wire } from '../../internal/src/modules/index.js';
|
|
2
|
+
type AgentDefinition = wire.AgentDefinition;
|
|
3
|
+
type ToolContent = wire.ToolContent;
|
|
4
|
+
type ToolRef = wire.ToolRef;
|
|
5
|
+
import { type ToolResult } from '../../internal/src/tool-output.js';
|
|
6
|
+
import type { Infer, StandardSchema } from './schema.js';
|
|
7
|
+
/** A role, or a catalogue model as provider:model. */
|
|
8
|
+
type Model = 'fast' | 'default' | 'strong' | (string & {});
|
|
9
|
+
/** The chat a tool call or a turn is of, as the platform names it in its signed call. */
|
|
10
|
+
export interface ChatContext {
|
|
11
|
+
/** The chat; '' for a call of a one-shot answer's (ai.ask with tools), which is of no chat. */
|
|
12
|
+
chatId: string;
|
|
13
|
+
/** The agent's name in the chat. */
|
|
14
|
+
agent: string;
|
|
15
|
+
/** The member whose message the turn answers; null: the project's server, or a turn nobody wrote. */
|
|
16
|
+
user: {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
} | null;
|
|
20
|
+
/**
|
|
21
|
+
* The chat's own labels — set by whoever started it, and a user may start
|
|
22
|
+
* one: what they say is the caller's word, not an authority (never a name
|
|
23
|
+
* of something they may then reach).
|
|
24
|
+
*/
|
|
25
|
+
metadata: Record<string, string>;
|
|
26
|
+
/** Ended when the platform stops waiting for the answer. */
|
|
27
|
+
signal: AbortSignal;
|
|
28
|
+
}
|
|
29
|
+
export interface ToolContext extends ChatContext {
|
|
30
|
+
/** The call's id: the same call is the same id, whatever copy of the machine it reaches. */
|
|
31
|
+
callId: string;
|
|
32
|
+
}
|
|
33
|
+
export type AgentContext = ChatContext;
|
|
34
|
+
export interface BackendToolOptions<S extends StandardSchema> {
|
|
35
|
+
/** What the agent calls it by: letters, digits, _ and -, starting with a letter, at most 64. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** What it does: the agent reads it to choose. */
|
|
38
|
+
description: string;
|
|
39
|
+
/** What it takes: a zod (valibot, arktype) object schema — the agent is shown it, and its input is checked against it. */
|
|
40
|
+
input: S;
|
|
41
|
+
/**
|
|
42
|
+
* What the call does: a string, an object (said as JSON), pictures
|
|
43
|
+
* (`{ mediaType, bytes }`), or a list of them — 4 MiB at most in all. A
|
|
44
|
+
* throw is the tool's failure: a ToolError's message (or the framework's
|
|
45
|
+
* own refusals', NotFound('…')) is told to the agent, anything else as
|
|
46
|
+
* "The tool failed".
|
|
47
|
+
*/
|
|
48
|
+
run(input: Infer<S>, ctx: ToolContext): ToolResult | Promise<ToolResult>;
|
|
49
|
+
/** Every call waits for a person's yes (a member of the chat, or the server). */
|
|
50
|
+
approval?: boolean;
|
|
51
|
+
/** The project's machine it runs on (default backend). */
|
|
52
|
+
machine?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface BackendTool<S extends StandardSchema = StandardSchema> {
|
|
55
|
+
readonly source: 'backend';
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly description: string;
|
|
58
|
+
readonly input: S;
|
|
59
|
+
/** The input's JSON Schema, as the agent is shown it. */
|
|
60
|
+
readonly parameters: Record<string, unknown>;
|
|
61
|
+
readonly approval: boolean;
|
|
62
|
+
readonly machine?: string;
|
|
63
|
+
/** The run, its input checked first: what the framework answers the platform's call with. */
|
|
64
|
+
call(input: unknown, ctx: ToolContext): Promise<{
|
|
65
|
+
output: ToolContent[];
|
|
66
|
+
is_error: boolean;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
export interface ClientToolOptions<S extends StandardSchema> {
|
|
70
|
+
name: string;
|
|
71
|
+
description: string;
|
|
72
|
+
/** What it takes; none: an empty object. */
|
|
73
|
+
input?: S;
|
|
74
|
+
approval?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** A tool the app's code answers (pb.ai.chat(id).tools({ name: fn })): the turn waits for its answer. */
|
|
77
|
+
export interface ClientTool {
|
|
78
|
+
readonly source: 'client';
|
|
79
|
+
readonly name: string;
|
|
80
|
+
readonly description: string;
|
|
81
|
+
readonly parameters: Record<string, unknown>;
|
|
82
|
+
readonly approval: boolean;
|
|
83
|
+
}
|
|
84
|
+
/** An HTTPS tool the project put (ai.endpoints.put(name, { kind: 'https', … })). */
|
|
85
|
+
export interface HttpsToolRef {
|
|
86
|
+
source: 'https';
|
|
87
|
+
name: string;
|
|
88
|
+
approval?: boolean;
|
|
89
|
+
}
|
|
90
|
+
/** An MCP server the project put: its tools, or the ones named in only. */
|
|
91
|
+
export interface McpToolRef {
|
|
92
|
+
source: 'mcp';
|
|
93
|
+
name: string;
|
|
94
|
+
only?: string[];
|
|
95
|
+
approval?: boolean;
|
|
96
|
+
}
|
|
97
|
+
export type Tool = BackendTool<StandardSchema> | ClientTool | HttpsToolRef | McpToolRef;
|
|
98
|
+
export interface SubagentDefinition {
|
|
99
|
+
/** Unique among the agent's subagents. */
|
|
100
|
+
name: string;
|
|
101
|
+
/** What it is for: the agent reads it to choose. */
|
|
102
|
+
description: string;
|
|
103
|
+
instructions?: string;
|
|
104
|
+
model?: Model;
|
|
105
|
+
tools?: Tool[];
|
|
106
|
+
/** The turn fails if this subagent's part fails. */
|
|
107
|
+
required?: boolean;
|
|
108
|
+
}
|
|
109
|
+
/** What an agent is for a turn. */
|
|
110
|
+
export interface AgentDefinitionInput {
|
|
111
|
+
instructions?: string;
|
|
112
|
+
model?: Model;
|
|
113
|
+
/** 64 at most, with its MCP servers'. */
|
|
114
|
+
tools?: Tool[];
|
|
115
|
+
/** 16 at most. */
|
|
116
|
+
subagents?: SubagentDefinition[];
|
|
117
|
+
/** How many tool calls a turn makes at most (1–200): past it the agent is told it has no tools left and answers. */
|
|
118
|
+
maxToolCalls?: number;
|
|
119
|
+
/** A named agent's: the app's users start chats with it themselves (when the project lets them). Left out, the server's alone. */
|
|
120
|
+
clients?: boolean;
|
|
121
|
+
}
|
|
122
|
+
/** What a backend's agent is for one turn: whether users reach it is the named agent's, said when it is put. */
|
|
123
|
+
export type TurnDefinition = Omit<AgentDefinitionInput, 'clients'>;
|
|
124
|
+
/** An agent the backend defines at each turn's start: put with ai.agents.put(agent), its chats' turns ask its machine. */
|
|
125
|
+
export interface BackendAgent {
|
|
126
|
+
readonly kind: 'agent';
|
|
127
|
+
readonly name: string;
|
|
128
|
+
/** The project's machine that defines it (default backend). */
|
|
129
|
+
readonly machine?: string;
|
|
130
|
+
/** The app's users start chats with it themselves (put with it). */
|
|
131
|
+
readonly clients: boolean;
|
|
132
|
+
/** The definition for a turn, said as the platform takes it, with the backend tools it hands out. */
|
|
133
|
+
resolve(ctx: AgentContext): Promise<{
|
|
134
|
+
definition: AgentDefinition;
|
|
135
|
+
tools: BackendTool[];
|
|
136
|
+
}>;
|
|
137
|
+
}
|
|
138
|
+
/** What this process declared, for the framework to serve. */
|
|
139
|
+
export interface DeclaredAI {
|
|
140
|
+
tools: Map<string, BackendTool>;
|
|
141
|
+
agents: Map<string, BackendAgent>;
|
|
142
|
+
/** Names declared twice (tool:<name>, agent:<name>): the framework refuses to start with one. */
|
|
143
|
+
twice: Set<string>;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A tool's failure in its own words: thrown from run, its message is what
|
|
147
|
+
* the agent — and the chat's members, who see the call — are told. Any other
|
|
148
|
+
* throw is told as "The tool failed" (a library's or the database's words
|
|
149
|
+
* may name what the chat is not to read); the whole of it is in the app's log.
|
|
150
|
+
*/
|
|
151
|
+
export declare class ToolError extends Error {
|
|
152
|
+
constructor(message: string);
|
|
153
|
+
}
|
|
154
|
+
export declare const AGENT_NAME: RegExp;
|
|
155
|
+
/** The input's JSON Schema: an object, written out (no references), as a provider takes a tool's parameters. */
|
|
156
|
+
export declare function parametersOf(schema: StandardSchema | undefined, what: string): Record<string, unknown>;
|
|
157
|
+
export declare function backendTool<S extends StandardSchema>(o: BackendToolOptions<S>): BackendTool<S>;
|
|
158
|
+
export declare function clientTool<S extends StandardSchema>(o: ClientToolOptions<S>): ClientTool;
|
|
159
|
+
export declare function backendAgent(name: string, define: (ctx: AgentContext) => TurnDefinition | Promise<TurnDefinition>, options?: {
|
|
160
|
+
machine?: string;
|
|
161
|
+
clients?: boolean;
|
|
162
|
+
}): BackendAgent;
|
|
163
|
+
/** A tool as the platform takes it (ToolRef). */
|
|
164
|
+
export declare function toolRef(t: Tool, what: string): ToolRef;
|
|
165
|
+
/** A definition as the platform takes it (AgentDefinition). */
|
|
166
|
+
export declare function definitionOf(d: AgentDefinitionInput, what: string): AgentDefinition;
|
|
167
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type PalbaseServer, type TokenUser, type TokenVerifier, type WebhookEvent } from '../../server/src/index.js';
|
|
2
2
|
import { type AuthSpec, type RateLimitConfig } from './decorators.js';
|
|
3
3
|
import { type ContractOptions } from './openapi.js';
|
|
4
|
+
import { type StringsTable } from './i18n.js';
|
|
4
5
|
import type { RequestTelemetry } from './requests.js';
|
|
5
6
|
/** The signed-in user, as @User() gives it. */
|
|
6
7
|
export interface UserT {
|
|
@@ -66,6 +67,12 @@ export interface AppOptions {
|
|
|
66
67
|
app: RequestDatabases;
|
|
67
68
|
requests: 'caller' | 'service';
|
|
68
69
|
};
|
|
70
|
+
/**
|
|
71
|
+
* The app's strings (palbase/strings/, loadStringsTable): t() answers each request in the first of its
|
|
72
|
+
* Accept-Language's languages the table has, the source language otherwise, and the answer says which
|
|
73
|
+
* (Content-Language) and that it depends on them (Vary: Accept-Language).
|
|
74
|
+
*/
|
|
75
|
+
strings?: StringsTable;
|
|
69
76
|
/**
|
|
70
77
|
* Built to write its contract and nothing else (PALBASE_CONTRACT_OUT): its
|
|
71
78
|
* webhooks' secrets are not read, and whether it declares a database says
|
|
@@ -90,7 +97,11 @@ export interface AppOptions {
|
|
|
90
97
|
* and answers 204, or the failure the platform tries again;
|
|
91
98
|
* - its @Upload routes' two steps, from storage (upload.ts), and its
|
|
92
99
|
* @Room classes' joins, messages and moments, from realtime (rooms.ts):
|
|
93
|
-
* platform-calls.ts
|
|
100
|
+
* platform-calls.ts;
|
|
101
|
+
* - its ai tools and agents (@palbase/ai's ai.tool and ai.agent), from the
|
|
102
|
+
* ai module: POST /_palbase/ai/tools runs the tool an agent called (its
|
|
103
|
+
* body's tool), POST /_palbase/ai/agents answers the definition of the
|
|
104
|
+
* agent a turn is of (its body's agent) — ai-calls.ts.
|
|
94
105
|
*/
|
|
95
106
|
platform?: {
|
|
96
107
|
secret: string;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The app's strings (palbase/strings/, loadStringsTable): its source language, the languages it has files for, and
|
|
3
|
+
* each key's translation by language — an empty one is not translated yet, and the key itself answers.
|
|
4
|
+
*/
|
|
2
5
|
export interface StringsTable {
|
|
3
|
-
version: 1;
|
|
4
6
|
source: string;
|
|
5
7
|
locales: string[];
|
|
6
|
-
strings: Record<string, Record<string,
|
|
7
|
-
value: string;
|
|
8
|
-
state: 'translated' | 'needs_review' | 'missing';
|
|
9
|
-
}>>;
|
|
8
|
+
strings: Record<string, Record<string, string>>;
|
|
10
9
|
}
|
|
11
|
-
/** The languages a request asks for, best first — each tag
|
|
10
|
+
/** The languages a request asks for, best first — each tag, then the shorter ones a table may name — and the source last. */
|
|
12
11
|
export declare function chainFromAcceptLanguage(header: string | null, source: string | undefined): string[];
|
|
13
12
|
/** The key's text in the first language of the chain that has it — the key itself in the source language. */
|
|
14
13
|
export declare function lookup(table: StringsTable, key: string, chain: readonly string[]): {
|
|
@@ -17,5 +16,7 @@ export declare function lookup(table: StringsTable, key: string, chain: readonly
|
|
|
17
16
|
};
|
|
18
17
|
/** {{name}} filled from vars ({{{name}}} left as it is); a value not given is empty. */
|
|
19
18
|
export declare function interpolate(text: string, vars?: Record<string, unknown>): string;
|
|
19
|
+
/** The app started without a table at `root`'s palbase/strings: the first t() says so. */
|
|
20
|
+
export declare function stringsMissingAt(root: string | undefined): void;
|
|
20
21
|
/** The sentence `key` (written in the source language) in the caller's language, {{name}} filled. */
|
|
21
22
|
export declare function t(key: string, vars?: Record<string, string | number | boolean | null | undefined>): string;
|
|
@@ -1,3 +1,30 @@
|
|
|
1
1
|
import type { StringsTable } from './i18n.js';
|
|
2
|
-
|
|
2
|
+
export declare const STRINGS_DIR = "palbase/strings";
|
|
3
|
+
/** The format this writes and the running app reads. */
|
|
4
|
+
export declare const STRINGS_FORMAT = 3;
|
|
5
|
+
/** A language tag as Intl writes it (en, en-US, zh-Hans): the form a request's languages are matched in. */
|
|
6
|
+
export declare function canonicalLanguage(tag: string): string | undefined;
|
|
7
|
+
/** The files as they are: their format, the source language, and each language's translations by key. */
|
|
8
|
+
export interface StringsFiles {
|
|
9
|
+
format: number;
|
|
10
|
+
source: string;
|
|
11
|
+
languages: Map<string, Map<string, string>>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* palbase/strings/ under `root` read as it is — this format, or the earlier one (its cells' values; a missing one
|
|
15
|
+
* is empty) — every file checked: a file that breaks a rule is named, with the rule. Undefined when there is none.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readStringsFiles(root: string, formats?: readonly number[]): Promise<StringsFiles | undefined>;
|
|
18
|
+
/** The table under `root` (the app's directory) the running app answers t() from, or undefined when it has none. */
|
|
3
19
|
export declare function loadStringsTable(root: string): Promise<StringsTable | undefined>;
|
|
20
|
+
/** One language file's text: its sentences in order, each with its translation, as JSON written for people to edit. */
|
|
21
|
+
export declare function stringsFileText(translations: ReadonlyMap<string, string>, order: readonly string[]): string;
|
|
22
|
+
/**
|
|
23
|
+
* palbase/strings/ under `root` written as `source` and `languages` (each with every key of `order`, in that
|
|
24
|
+
* order), each file written beside itself and renamed into place, a language file no longer in `languages`
|
|
25
|
+
* removed. A run cut short leaves a table the next one reads: a new table's _meta.json is written first (its
|
|
26
|
+
* language files come with it), an existing one's last (a conversion from the earlier format reads the files it
|
|
27
|
+
* already wrote — readStringsFiles takes a plain translation there too). The files whose text changed, by name;
|
|
28
|
+
* a file that differs only in its line endings (a checkout's CRLF) is not one.
|
|
29
|
+
*/
|
|
30
|
+
export declare function writeStringsFiles(root: string, source: string, languages: ReadonlyMap<string, ReadonlyMap<string, string>>, order: readonly string[]): Promise<string[]>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ChatMessage } from './modules/ai.js';
|
|
2
|
+
export type TurnStatus = 'running' | 'waiting' | 'completed' | 'failed' | 'canceled' | 'timed_out' | 'budget_exceeded';
|
|
3
|
+
export type WaitingReason = 'approval' | 'tool_output' | 'subagents' | 'message';
|
|
4
|
+
export type ToolStatus = 'running' | 'awaiting_output' | 'awaiting_approval' | 'approved' | 'denied' | 'expired' | 'completed' | 'failed' | 'canceled';
|
|
5
|
+
/** A member's (or the server's) message, as the agent got it. */
|
|
6
|
+
export interface MessageEvent {
|
|
7
|
+
type: 'message';
|
|
8
|
+
/** The journal's sequence. */
|
|
9
|
+
seq: number;
|
|
10
|
+
message: ChatMessage;
|
|
11
|
+
}
|
|
12
|
+
/** A turn's state: started, waiting (and why), or ended. */
|
|
13
|
+
export interface TurnEvent {
|
|
14
|
+
type: 'turn';
|
|
15
|
+
seq: number;
|
|
16
|
+
response_id: string;
|
|
17
|
+
/** The member who started it; null: the project's server's turn, or one the engine started from late messages. */
|
|
18
|
+
user_id?: string | null;
|
|
19
|
+
/** On running and waiting: the chat has had one member all its life, as it is now (once a second has been added, never again). */
|
|
20
|
+
solo?: boolean;
|
|
21
|
+
status: TurnStatus;
|
|
22
|
+
/** Why it waits, and where in its answer — and, for a tool call, which: null unless status is waiting. */
|
|
23
|
+
waiting: {
|
|
24
|
+
reason: WaitingReason;
|
|
25
|
+
text_offset: number;
|
|
26
|
+
approval_id?: string;
|
|
27
|
+
tool_call_id?: string;
|
|
28
|
+
tool_name?: string;
|
|
29
|
+
input?: unknown;
|
|
30
|
+
} | null;
|
|
31
|
+
error: {
|
|
32
|
+
code?: string;
|
|
33
|
+
message?: string;
|
|
34
|
+
} | string | null;
|
|
35
|
+
}
|
|
36
|
+
/** The agent's answer as it is written: new text only, at its byte offset. */
|
|
37
|
+
export interface TextEvent {
|
|
38
|
+
type: 'text';
|
|
39
|
+
response_id: string;
|
|
40
|
+
/** Where the text goes, in bytes of the answer (UTF-8). */
|
|
41
|
+
offset: number;
|
|
42
|
+
text: string;
|
|
43
|
+
}
|
|
44
|
+
/** Discard the answer past offset (bytes): the model went back. */
|
|
45
|
+
export interface ResetEvent {
|
|
46
|
+
type: 'reset';
|
|
47
|
+
response_id: string;
|
|
48
|
+
offset: number;
|
|
49
|
+
}
|
|
50
|
+
/** A tool call, each time its state changes. */
|
|
51
|
+
export interface ToolEvent {
|
|
52
|
+
type: 'tool';
|
|
53
|
+
seq: number;
|
|
54
|
+
response_id: string;
|
|
55
|
+
/** The subagent's run that made it; absent or null: the agent's own. */
|
|
56
|
+
run_id?: string | null;
|
|
57
|
+
call_id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
status: ToolStatus;
|
|
60
|
+
/** Where in its response's answer it happened, in bytes. */
|
|
61
|
+
text_offset: number;
|
|
62
|
+
input?: unknown;
|
|
63
|
+
output?: unknown;
|
|
64
|
+
/** Set while it waits for a person's yes (awaiting_approval). */
|
|
65
|
+
approval_id?: string | null;
|
|
66
|
+
/** The output's pictures. */
|
|
67
|
+
image_count?: number;
|
|
68
|
+
is_error?: boolean;
|
|
69
|
+
/** The subagent's response that made the call, when one did. */
|
|
70
|
+
agent_response_id?: string | null;
|
|
71
|
+
[field: string]: unknown;
|
|
72
|
+
}
|
|
73
|
+
/** A subagent at work — or one the agent asked for and was refused (status denied, with its reason). */
|
|
74
|
+
export interface AgentEvent {
|
|
75
|
+
type: 'agent';
|
|
76
|
+
seq: number;
|
|
77
|
+
/** The subagent's run; '' for a refused one, which never ran. */
|
|
78
|
+
run_id: string;
|
|
79
|
+
/** The subagent's own response; '' for a refused one. */
|
|
80
|
+
response_id: string;
|
|
81
|
+
/** The response that handed it the work: its text_offset is in that one's answer. */
|
|
82
|
+
parent_response_id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
required: boolean;
|
|
85
|
+
status: string;
|
|
86
|
+
text_offset: number;
|
|
87
|
+
/** Why it was refused, or why it ended as it did. */
|
|
88
|
+
reason?: string;
|
|
89
|
+
[field: string]: unknown;
|
|
90
|
+
}
|
|
91
|
+
export type ChatEvent = MessageEvent | TurnEvent | TextEvent | ResetEvent | ToolEvent | AgentEvent;
|
|
92
|
+
/** Where a reader is: what it hands the stream to go on from there. */
|
|
93
|
+
export interface ChatPosition {
|
|
94
|
+
/** The id of the last journal event held. */
|
|
95
|
+
after?: number;
|
|
96
|
+
/** The response whose answer is being read, and the bytes of it held. */
|
|
97
|
+
response?: string;
|
|
98
|
+
offset?: number;
|
|
99
|
+
}
|
|
100
|
+
export interface FollowChatOptions extends ChatPosition {
|
|
101
|
+
signal?: AbortSignal;
|
|
102
|
+
/** How long a stream that keeps being cut is followed again before the reader is told (default 2 minutes). */
|
|
103
|
+
resumeFor?: number;
|
|
104
|
+
/** A stream with no byte for this long is taken for cut (default 45 s; the module's heartbeat is more often). */
|
|
105
|
+
idleFor?: number;
|
|
106
|
+
}
|
|
107
|
+
/** The stream's events; its position is where a reader that stops now would go on from. */
|
|
108
|
+
export interface ChatStream extends AsyncIterable<ChatEvent> {
|
|
109
|
+
readonly position: ChatPosition;
|
|
110
|
+
}
|
|
111
|
+
/** Opens the stream from a point: the generated client's followChat, through the caller's transport. */
|
|
112
|
+
export type OpenChatStream = (query: ChatPosition, signal: AbortSignal) => Promise<globalThis.Response>;
|
|
113
|
+
/** A chat refusal of the module's, said in words its caller can act on; anything else (or one said already) as it was. */
|
|
114
|
+
export declare function chatRefusal(e: unknown): unknown;
|
|
115
|
+
/** The same promise, its chat refusals said as chatRefusal says them. */
|
|
116
|
+
export declare function told<T>(p: Promise<T>): Promise<T>;
|
|
117
|
+
export declare function followChat(open: OpenChatStream, options?: FollowChatOptions): ChatStream;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ToolContent } from './modules/ai.js';
|
|
2
|
+
export type ToolPart = {
|
|
3
|
+
type: 'text';
|
|
4
|
+
text: string;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'image';
|
|
7
|
+
mediaType: string;
|
|
8
|
+
data: Uint8Array | string;
|
|
9
|
+
} | {
|
|
10
|
+
type: 'image';
|
|
11
|
+
media_type: string;
|
|
12
|
+
data: string;
|
|
13
|
+
} | {
|
|
14
|
+
mediaType: string;
|
|
15
|
+
bytes: Uint8Array;
|
|
16
|
+
};
|
|
17
|
+
/** Anything a tool returns: its parts, a string, or a value said as JSON. */
|
|
18
|
+
export type ToolResult = string | ToolPart | readonly ToolPart[] | object | number | boolean | null | undefined;
|
|
19
|
+
export declare function toolOutput(value: unknown): ToolContent[];
|
|
20
|
+
export declare function base64(bytes: Uint8Array): string;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { type AgentEvent, type ToolEvent, type TurnEvent } from '../../internal/src/chat.js';
|
|
2
|
+
import { ai as aiModule } from '../../internal/src/modules/index.js';
|
|
3
|
+
import { type ToolResult } from '../../internal/src/tool-output.js';
|
|
4
|
+
import { type Transport } from '../../internal/src/transport.js';
|
|
5
|
+
import type { SessionManager } from './session.js';
|
|
6
|
+
export type Chat = aiModule.Chat;
|
|
7
|
+
export type { AgentEvent, ChatEvent, ToolEvent, TurnEvent } from '../../internal/src/chat.js';
|
|
8
|
+
export type { ToolResult } from '../../internal/src/tool-output.js';
|
|
9
|
+
/** A member's message as this client holds it: one it sent is here at once, pending until the agent has it. */
|
|
10
|
+
export interface ChatMessageView {
|
|
11
|
+
/** The writer's id for it: the same id is the same message. */
|
|
12
|
+
clientId: string;
|
|
13
|
+
/** The module's id; null until it answered. */
|
|
14
|
+
id: string | null;
|
|
15
|
+
text: string;
|
|
16
|
+
/** The member who wrote it; null: the project's server (or, for one this client sent, not yet answered — mine says it is the user's). */
|
|
17
|
+
author: string | null;
|
|
18
|
+
authorName: string;
|
|
19
|
+
delivery: string;
|
|
20
|
+
/** pending until the agent has it, delivered once it does, failed when it could not be given (or sent). */
|
|
21
|
+
status: 'pending' | 'delivered' | 'failed';
|
|
22
|
+
/** Why it failed: the module's code. */
|
|
23
|
+
error: string | null;
|
|
24
|
+
/** The turn it started or reached. */
|
|
25
|
+
responseId: string | null;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
/** Sent from this client. */
|
|
28
|
+
mine: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** A piece of an answer, in order: its text, split where a tool was called or a subagent handed work. */
|
|
31
|
+
export type ChatPart = {
|
|
32
|
+
type: 'text';
|
|
33
|
+
text: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'tool';
|
|
36
|
+
tool: ToolEvent;
|
|
37
|
+
} | {
|
|
38
|
+
type: 'agent';
|
|
39
|
+
agent: AgentEvent;
|
|
40
|
+
};
|
|
41
|
+
/** One answer of the agent's (or of one of its subagents). */
|
|
42
|
+
export interface ChatResponse {
|
|
43
|
+
id: string;
|
|
44
|
+
status: TurnEvent['status'] | null;
|
|
45
|
+
waiting: TurnEvent['waiting'];
|
|
46
|
+
error: TurnEvent['error'];
|
|
47
|
+
/** The whole answer so far. */
|
|
48
|
+
text: string;
|
|
49
|
+
parts: ChatPart[];
|
|
50
|
+
/** A subagent's: its run, its name, and the response that handed it the work. */
|
|
51
|
+
runId: string | null;
|
|
52
|
+
agent: string | null;
|
|
53
|
+
parentId: string | null;
|
|
54
|
+
/** The member whose turn it is (a subagent's: its turn's); null: the project's server's, or the engine's. */
|
|
55
|
+
startedBy: string | null;
|
|
56
|
+
/** The signed-in user's own turn: theirs alone to answer its client tools and decide its approvals. */
|
|
57
|
+
mine: boolean;
|
|
58
|
+
/** The chat has had one member all its life, as its turn last said (null until it did): only then does this app answer its client tools by itself. */
|
|
59
|
+
solo: boolean | null;
|
|
60
|
+
}
|
|
61
|
+
export type ChatItem = {
|
|
62
|
+
type: 'message';
|
|
63
|
+
message: ChatMessageView;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'response';
|
|
66
|
+
response: ChatResponse;
|
|
67
|
+
};
|
|
68
|
+
export interface ChatState {
|
|
69
|
+
/** idle until the chat is first used; live while its stream is open; failed when the stream was refused (error says why). */
|
|
70
|
+
status: 'idle' | 'connecting' | 'live' | 'failed' | 'closed';
|
|
71
|
+
error: unknown;
|
|
72
|
+
/** The conversation in order: the members' messages and the agent's answers (a subagent's is in its parent's parts). */
|
|
73
|
+
items: ChatItem[];
|
|
74
|
+
messages: ChatMessageView[];
|
|
75
|
+
/** Every answer, the subagents' too, as they began. */
|
|
76
|
+
responses: ChatResponse[];
|
|
77
|
+
/** The last turn's state. */
|
|
78
|
+
turn: TurnEvent | null;
|
|
79
|
+
/**
|
|
80
|
+
* What waits for this user: tool calls of their own turns waiting for
|
|
81
|
+
* their yes (approve/deny), and client tool calls of their own turns
|
|
82
|
+
* waiting for their app's answer. Another member's turn is theirs to
|
|
83
|
+
* decide — its calls are in its parts, not here.
|
|
84
|
+
*/
|
|
85
|
+
waiting: {
|
|
86
|
+
approvals: ToolEvent[];
|
|
87
|
+
outputs: ToolEvent[];
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export interface ChatOptions {
|
|
91
|
+
/** The journal id to follow from (default 0: the chat from its start). */
|
|
92
|
+
after?: number;
|
|
93
|
+
/** How long a stream that keeps being cut is followed again before the chat fails (default 2 minutes). */
|
|
94
|
+
resumeFor?: number;
|
|
95
|
+
}
|
|
96
|
+
/** A client tool's answer, by the tool's name: what it returns is shown to the agent; a throw is the tool's failure. */
|
|
97
|
+
export type ClientToolHandlers = Record<string, (input: unknown, call: ToolEvent) => ToolResult | Promise<ToolResult>>;
|
|
98
|
+
/** One chat, followed: its state, its changes, and what the user does in it. */
|
|
99
|
+
export declare class ChatSession implements AsyncIterable<ChatState> {
|
|
100
|
+
readonly id: string;
|
|
101
|
+
private readonly api;
|
|
102
|
+
private readonly session;
|
|
103
|
+
private readonly options;
|
|
104
|
+
private status;
|
|
105
|
+
private error;
|
|
106
|
+
private readonly messages;
|
|
107
|
+
private readonly responses;
|
|
108
|
+
private readonly order;
|
|
109
|
+
private turn;
|
|
110
|
+
private readonly calls;
|
|
111
|
+
private readonly handlers;
|
|
112
|
+
private readonly answering;
|
|
113
|
+
private readonly listeners;
|
|
114
|
+
private following;
|
|
115
|
+
/** Where the stream is: a chat followed again (after a failure) goes on from there, not from its start. */
|
|
116
|
+
private position;
|
|
117
|
+
private snapshot;
|
|
118
|
+
private told;
|
|
119
|
+
constructor(api: ReturnType<typeof aiModule.client>, id: string, options?: ChatOptions, session?: SessionManager);
|
|
120
|
+
/** The chat as this client holds it now: the same object until it changes. */
|
|
121
|
+
get state(): ChatState;
|
|
122
|
+
/** Told the state each time it changes (at most once a microtask); returns the unsubscribe. The chat is followed from now on. */
|
|
123
|
+
subscribe(listener: (state: ChatState) => void): () => void;
|
|
124
|
+
/** The state now, then each time it changes; a break leaves the chat followed (close() ends it). */
|
|
125
|
+
[Symbol.asyncIterator](): AsyncGenerator<ChatState, void, undefined>;
|
|
126
|
+
/**
|
|
127
|
+
* Writes to the chat: the message is in the state at once, pending, and
|
|
128
|
+
* delivered when the agent has it (the stream says so); failed when it
|
|
129
|
+
* could not be sent — the promise rejects with why (429
|
|
130
|
+
* too_many_messages, 409 history_full). The same clientId again (a
|
|
131
|
+
* failed message sent again) is the same message.
|
|
132
|
+
*/
|
|
133
|
+
send(text: string, options?: {
|
|
134
|
+
delivery?: 'next' | 'now';
|
|
135
|
+
clientId?: string;
|
|
136
|
+
}): Promise<ChatMessageView>;
|
|
137
|
+
/**
|
|
138
|
+
* The client tools this app answers by itself, by name: a call of one of
|
|
139
|
+
* them that waits for its output (a tool part, awaiting_output) is
|
|
140
|
+
* answered with what the function returns — in a turn the signed-in user
|
|
141
|
+
* started, of a chat that is theirs alone (solo). In a chat with other
|
|
142
|
+
* members a message of theirs may have asked for it (a location, a
|
|
143
|
+
* photo), and they see the answer: the call stays in waiting.outputs for
|
|
144
|
+
* the app to ask its user and answer (answer()). A call in another
|
|
145
|
+
* member's turn is theirs: it never runs on this device (the module
|
|
146
|
+
* refuses another's answer, 403 not_your_turn). Returns the function that
|
|
147
|
+
* takes them back.
|
|
148
|
+
*/
|
|
149
|
+
tools(handlers: ClientToolHandlers): () => void;
|
|
150
|
+
/**
|
|
151
|
+
* Answers a client tool's call of the user's own turn (a tool part's
|
|
152
|
+
* call_id, awaiting_output) — what tools() does by itself in a chat that is
|
|
153
|
+
* theirs alone, and the app does here when it is not, once its user said
|
|
154
|
+
* yes. True when this answer is the one; false when the call was answered
|
|
155
|
+
* already or waits no more.
|
|
156
|
+
*/
|
|
157
|
+
answer(callId: string, output: ToolResult, options?: {
|
|
158
|
+
isError?: boolean;
|
|
159
|
+
}): Promise<boolean>;
|
|
160
|
+
/** A call that waits for a person's yes (a tool part's approval_id): made. */
|
|
161
|
+
approve(approvalId: string, reason?: string): Promise<void>;
|
|
162
|
+
/** Not made: the agent is told, with the reason, and goes on. */
|
|
163
|
+
deny(approvalId: string, reason?: string): Promise<void>;
|
|
164
|
+
/** Stops the turn the agent is working on: what it wrote stays. Its response's id, or null when none ran. */
|
|
165
|
+
stop(): Promise<string | null>;
|
|
166
|
+
/** A message for one of the agent's subagents at work (an agent part's run_id), at its next step. */
|
|
167
|
+
sendToSubagent(runId: string, text: string): Promise<void>;
|
|
168
|
+
/** Stops following the chat. */
|
|
169
|
+
close(): void;
|
|
170
|
+
private follow;
|
|
171
|
+
private take;
|
|
172
|
+
private heard;
|
|
173
|
+
private response;
|
|
174
|
+
private autoAnswer;
|
|
175
|
+
/** The signed-in user started this response's turn (its turn event said so). */
|
|
176
|
+
private mine;
|
|
177
|
+
private changed;
|
|
178
|
+
private build;
|
|
179
|
+
}
|
|
180
|
+
/** The project's agents for the signed-in user. */
|
|
181
|
+
export declare class AI {
|
|
182
|
+
/** Every client operation of the ai contract, through the session's transport. */
|
|
183
|
+
readonly api: ReturnType<typeof aiModule.client>;
|
|
184
|
+
private readonly session;
|
|
185
|
+
/** The chats the user is in: started with a named agent the project opened to its users (as its owner), listed, read, deleted (an owner's). */
|
|
186
|
+
readonly chats: {
|
|
187
|
+
/** A chat with a named agent (clients: true), the user its owner under `name`. A user's chat carries no metadata: that is the server's to write. */
|
|
188
|
+
create(chat: {
|
|
189
|
+
agent: string;
|
|
190
|
+
name?: string;
|
|
191
|
+
title?: string;
|
|
192
|
+
}): Promise<Chat>;
|
|
193
|
+
/** The chats the user is in, the newest first (100 at most). */
|
|
194
|
+
list(): Promise<Chat[]>;
|
|
195
|
+
get(id: string): Promise<Chat>;
|
|
196
|
+
delete(id: string): Promise<void>;
|
|
197
|
+
};
|
|
198
|
+
constructor(transport: Transport, session?: SessionManager);
|
|
199
|
+
/** A chat the user is in, to follow and write to. */
|
|
200
|
+
chat(chatId: string, options?: ChatOptions): ChatSession;
|
|
201
|
+
}
|
|
@@ -5,6 +5,7 @@ import { type SessionStore } from './session.js';
|
|
|
5
5
|
import { RealtimeSocket } from './socket.js';
|
|
6
6
|
import { Analytics, type AnalyticsOptions } from './analytics.js';
|
|
7
7
|
import { Purchases } from './purchases.js';
|
|
8
|
+
import { AI } from './ai.js';
|
|
8
9
|
export interface ClientOptions {
|
|
9
10
|
/** The project's address: https://<ref>.<domain> */
|
|
10
11
|
url: string;
|
|
@@ -53,5 +54,7 @@ export interface PalbaseClient {
|
|
|
53
54
|
analytics: Analytics;
|
|
54
55
|
/** What the signed-in customer has (live, onCustomerInfo), the paywall's catalog, and buying on the web. */
|
|
55
56
|
purchases: Purchases;
|
|
57
|
+
/** The project's agents, for the signed-in user (when the project lets its users talk to them): their chats, followed as typed parts. */
|
|
58
|
+
ai: AI;
|
|
56
59
|
}
|
|
57
60
|
export declare function createClient(opts: ClientOptions): PalbaseClient;
|
package/types/web/src/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type { Auth, CredentialsLike, SignInResult, User } from './auth.js';
|
|
|
3
3
|
export { localStore, memoryStore, SessionChangedError, type AuthEvent, type Session, type SessionStore } from './session.js';
|
|
4
4
|
export { Channel, RealtimeSocket, type PushReply } from './socket.js';
|
|
5
5
|
export { Analytics, type AnalyticsOptions } from './analytics.js';
|
|
6
|
+
export { AI, ChatSession, type AgentEvent, type Chat, type ChatEvent, type ChatItem, type ChatMessageView, type ChatOptions, type ChatPart, type ChatResponse, type ChatState, type ClientToolHandlers, type ToolEvent, type ToolResult, type TurnEvent, } from './ai.js';
|
|
6
7
|
export { Purchases, type CustomerInfo, type CustomerInfoListener, type MergeGrant, type Offerings } from './purchases.js';
|
|
7
8
|
export type { Paywall, PaywallRenderOptions } from './paywall/placement.js';
|
|
8
9
|
export type { PaywallAttribute } from './paywall/attributes.js';
|
package/dist/chunks/QA3GIZMI.js
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
/*! palbase 0.3.2 — Palbase SDK License (see LICENSE) */
|
|
2
|
-
import{AsyncLocalStorage as g}from"node:async_hooks";function b(e,r){return new Proxy(r,{get(t,n,s){if(d.getStore()?.services===void 0)return Reflect.get(t,n,s);let o=f(e,n,t),a=o[n];return typeof a=="function"?a.bind(o):a}})}function f(e,r,t){let n=d.getStore()?.services;if(n===void 0)return t;let s=n[e];if(s!==void 0)return s;if(e==="Log"||typeof r=="symbol"||r==="then")return t;throw new Error(`${e}.${String(r)} was read, but this test did not provide ${e}. Pass it: withServices({ ${e}: \u2026 }, () => \u2026)`)}var p=Symbol.for("palbase.backend.requestScope"),d=globalThis[p]??=new g,u;function S(e){u=e}var m=new Proxy({},{get(e,r){if(!u)throw new Error(`Palbase.${String(r)} is reached before the app is served (serve() binds it)`);return u[r]}}),v=new Proxy({},{get(e,r){let t=d.getStore(),n=t?.database;if(!n)throw new Error(t?.services?`Database.${String(r)} was read, and this test gave no Database: withServices({ Database: fake.raw }, \u2026)`:t?`Database.${String(r)}: this app declares no database \u2014 PalbaseFactory.create(\u2026, { db: { schemas } })`:`Database.${String(r)} is reached outside a request: it is the request's transaction, reached from a handler, a service it calls or an @On`);let s=n[r];return typeof s=="function"?s.bind(n):s}});function c(e,r,t){let n={time:new Date().toISOString(),level:e,msg:r},s=d.getStore();if(s&&(n.request_id=s.requestId),t)for(let[a,i]of Object.entries(t))n[a]=i instanceof Error?{name:i.name,message:i.message,stack:i.stack}:i;let o=w(n);e==="error"||e==="warn"?process.stderr.write(`${o}
|
|
3
|
-
`):process.stdout.write(`${o}
|
|
4
|
-
`)}function w(e){try{return JSON.stringify(e)}catch{let r=new WeakSet;try{return JSON.stringify(e,(t,n)=>{if(typeof n=="bigint")return n.toString();if(typeof n=="object"&&n!==null){if(r.has(n))return"[circular]";r.add(n)}return n})}catch{return JSON.stringify({time:e.time,level:e.level,msg:e.msg,request_id:e.request_id,unwritable:"a field of this line could not be written"})}}}var y=b("Log",{debug:(e,r)=>c("debug",e,r),info:(e,r)=>c("info",e,r),warn:(e,r)=>c("warn",e,r),error:(e,r)=>c("error",e,r)});export{b as a,f as b,d as c,S as d,m as e,v as f,y as g};
|
package/dist/chunks/QD5XLRIJ.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
/*! palbase 0.3.2 — Palbase SDK License (see LICENSE) */
|
|
2
|
-
import{c as f}from"./QA3GIZMI.js";var A=512,b=8,x=64,O=/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/,p={"i-klingon":"tlh","en-gb-oed":"en-GB-oxendict","sgn-be-fr":"sfb","zh-cmn-hans":"cmn-Hans","zh-yue":"yue","hy-arevela":"hy-arevela"};function m(e){let n=e.indexOf("-");return n>0?e.slice(0,n):e}function _(e){let n=e.split("-"),r=(n[0]??"").toLowerCase();return r==="mo"&&n.length===1?"ro-MD":r==="tl"?(n[0]="fil",n.join("-")):r==="sh"?(n[0]="sr",n.length>1&&/^[A-Za-z]{4}$/.test(n[1]??"")||n.splice(1,0,"Latn"),n.join("-")):e}function E(e){let n=e.trim().replaceAll("_","-");if(n===""||n==="*"||n.length>x)return;let r=n.toLowerCase(),s=Object.hasOwn(p,r)?p[r]:void 0;if(s===void 0){try{s=Intl.getCanonicalLocales(_(n))[0]}catch{return}if(s===void 0)return}let i=m(s);if(!(i===""||i==="und"||i==="mul"))return s}function S(e,n){let r=[],s=i=>{i!==""&&!r.includes(i)&&r.push(i)};if(e!==null&&e.length<=A){let i=[];for(let o of e.split(",")){let t=o.trim();if(t==="")continue;let c=1,u=t.indexOf(";");if(u>=0){let l=t.slice(u+1);t=t.slice(0,u).trim();let a=l.indexOf("q=");if(a>=0){let d=l.slice(a+2).trim();if(O.test(d)){let g=Number(d);Number.isFinite(g)&&(c=g)}}}c>0&&i.push({tag:t,q:c,at:i.length})}i.sort((o,t)=>t.q-o.q||o.at-t.at);for(let{tag:o}of i.slice(0,b)){let t=E(o);t!==void 0&&(s(t),s(m(t)))}}return n!==void 0&&n!==""&&s(n),r}function L(e,n,r){let s=e.strings??{},i=Object.hasOwn(s,n),o=i?s[n]:null;for(let t of r){if(t===e.source){if(i)return{text:n,locale:t};continue}let c=o!=null&&Object.hasOwn(o,t)?o[t]:void 0;if(c!=null&&c.state!=="missing")return{text:c.value,locale:t}}return{text:n,locale:e.source}}var w=/\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;function h(e,n){return e.replace(w,(r,s,i,o)=>{if(o[i-1]==="{"||o[i+r.length]==="}")return r;let t=n!==void 0&&Object.hasOwn(n,s)?n[s]:void 0;return t==null?"":String(t)})}function T(e,n){let r=f.getStore(),s=r?.strings;if(s===void 0)return h(e,n);let{text:i,locale:o}=L(s,e,r?.locales??[s.source]),t=r?.usedLocales;return t!==void 0&&!t.includes(o)&&t.push(o),h(i,n)}export{S as a,T as b};
|