@managoat/fountain-sdk 1.25.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +653 -0
  2. package/LICENSE +202 -0
  3. package/README.md +445 -0
  4. package/dist/client.d.ts +190 -0
  5. package/dist/client.js +225 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/config.d.ts +49 -0
  8. package/dist/config.js +87 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/conversation.d.ts +100 -0
  11. package/dist/conversation.js +189 -0
  12. package/dist/conversation.js.map +1 -0
  13. package/dist/errors.d.ts +102 -0
  14. package/dist/errors.js +197 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/generated/openapi.d.ts +16654 -0
  17. package/dist/generated/openapi.js +6 -0
  18. package/dist/generated/openapi.js.map +1 -0
  19. package/dist/http.d.ts +37 -0
  20. package/dist/http.js +129 -0
  21. package/dist/http.js.map +1 -0
  22. package/dist/index.d.ts +14 -0
  23. package/dist/index.js +13 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/node.d.ts +2 -0
  26. package/dist/node.js +21 -0
  27. package/dist/node.js.map +1 -0
  28. package/dist/queue.d.ts +25 -0
  29. package/dist/queue.js +64 -0
  30. package/dist/queue.js.map +1 -0
  31. package/dist/resolve.d.ts +29 -0
  32. package/dist/resolve.js +89 -0
  33. package/dist/resolve.js.map +1 -0
  34. package/dist/resources.d.ts +126 -0
  35. package/dist/resources.js +206 -0
  36. package/dist/resources.js.map +1 -0
  37. package/dist/run.d.ts +81 -0
  38. package/dist/run.js +247 -0
  39. package/dist/run.js.map +1 -0
  40. package/dist/schemas.d.ts +90 -0
  41. package/dist/schemas.js +2 -0
  42. package/dist/schemas.js.map +1 -0
  43. package/dist/sse.d.ts +58 -0
  44. package/dist/sse.js +219 -0
  45. package/dist/sse.js.map +1 -0
  46. package/dist/team.d.ts +90 -0
  47. package/dist/team.js +183 -0
  48. package/dist/team.js.map +1 -0
  49. package/dist/turn.d.ts +46 -0
  50. package/dist/turn.js +205 -0
  51. package/dist/turn.js.map +1 -0
  52. package/dist/types.d.ts +144 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +61 -0
@@ -0,0 +1,189 @@
1
+ import { Run } from "./run.js";
2
+ import { streamEvents } from "./sse.js";
3
+ import { conversationUrl } from "./config.js";
4
+ /**
5
+ * A conversation you already have — the sandbox is still there, and so is
6
+ * everything the agent learned in it.
7
+ *
8
+ * This is the piece no stateless agent API has. `resume(id).send(...)` costs
9
+ * one prompt, not a re-explanation of the whole task, because the machine, the
10
+ * checkout and the session are exactly where the last turn left them.
11
+ */
12
+ export class Conversation {
13
+ id;
14
+ http;
15
+ /** Where the log feed has been read to, so a follow-up skips the history. */
16
+ cursorValue;
17
+ constructor(http, id, cursor = 0) {
18
+ this.http = http;
19
+ this.id = id;
20
+ this.cursorValue = cursor;
21
+ }
22
+ /** Where a human watches this conversation. */
23
+ get url() {
24
+ return conversationUrl(this.id, this.http.config);
25
+ }
26
+ /** The conversation record: status, agent, turn count. */
27
+ async get() {
28
+ return this.http.data("GET", `/api/conversations/${this.id}`);
29
+ }
30
+ async status() {
31
+ return (await this.get()).status;
32
+ }
33
+ async turns() {
34
+ return this.http.list(`/api/conversations/${this.id}/turns`);
35
+ }
36
+ /** Send the next turn. Returns a `Run` — await it, or stream it. */
37
+ send(prompt, options = {}) {
38
+ const body = { prompt };
39
+ if (options.images?.length)
40
+ body.images = options.images;
41
+ const run = new Run(this.http, {
42
+ start: async () => {
43
+ // The cursor and the turn number both have to be taken *before* the
44
+ // prompt goes in, or we race the events it produces.
45
+ const after = await this.cursor();
46
+ const turnNumber = (await this.lastTurnNumber()) + 1;
47
+ await this.http.request("POST", `/api/conversations/${this.id}/prompts`, { body });
48
+ const conversation = await this.get();
49
+ return { conversation, turnNumber, after };
50
+ },
51
+ }, options);
52
+ // Keep the conversation's cursor moving as the run consumes the feed, so
53
+ // the next send starts where this one stopped.
54
+ void run
55
+ .finally(() => {
56
+ if (run.cursor > this.cursorValue)
57
+ this.cursorValue = run.cursor;
58
+ })
59
+ .catch(() => { });
60
+ return run;
61
+ }
62
+ /**
63
+ * Answer a permission request the agent is holding a tool call on.
64
+ *
65
+ * `optionId` has to be one of the ids the agent offered on the
66
+ * `permission_request` block — the server refuses anything else with a 422
67
+ * rather than forwarding it. Answer promptly: the request expires, and an
68
+ * expired one is denied.
69
+ *
70
+ * ```ts
71
+ * for await (const event of run) {
72
+ * if (event.type === "permission") {
73
+ * const allow = event.request.options.find((o) => o.kind === "allow_once");
74
+ * if (allow) await conversation.answer(event.request.requestId, allow.optionId);
75
+ * }
76
+ * }
77
+ * ```
78
+ */
79
+ async answer(requestId, optionId) {
80
+ await this.http.request("POST", `/api/conversations/${this.id}/requests/${encodeURIComponent(requestId)}`, { body: { option_id: optionId } });
81
+ }
82
+ /**
83
+ * Mark everything so far as read, clearing the teammate's unread badge.
84
+ *
85
+ * Every application built on Fountain calls this — it is what stops a UI
86
+ * shouting about messages the person is currently looking at.
87
+ */
88
+ async markRead() {
89
+ await this.http.request("POST", `/api/conversations/${this.id}/read`);
90
+ }
91
+ /**
92
+ * Everything the feed holds, oldest first, paged until drained.
93
+ *
94
+ * The other universal one: a UI opening a thread needs the transcript so
95
+ * far, and the JSON feed pages at 1000. `streams` narrows what comes back —
96
+ * `["acp"]` for a transcript, `["stage"]` for the lifecycle alone.
97
+ */
98
+ async history(options = {}) {
99
+ const streams = Array.isArray(options.streams) ? options.streams.join(",") : options.streams;
100
+ const limit = options.limit ?? 1000;
101
+ const out = [];
102
+ let after = options.after ?? 0;
103
+ for (;;) {
104
+ const page = await this.http.request("GET", `/api/conversations/${this.id}/events`, {
105
+ query: { after, limit, blocks: "true", streams },
106
+ });
107
+ const events = page?.data ?? [];
108
+ out.push(...events);
109
+ const meta = page?.meta ?? {};
110
+ if (!meta.has_more || meta.next_cursor === null || meta.next_cursor === undefined)
111
+ break;
112
+ after = meta.next_cursor;
113
+ }
114
+ const last = out.at(-1)?.id;
115
+ if (typeof last === "number" && last > this.cursorValue)
116
+ this.cursorValue = last;
117
+ return out;
118
+ }
119
+ /** The conversations this one spawned, as a tree. */
120
+ async tree() {
121
+ return this.http.data("GET", `/api/conversations/${this.id}/tree`);
122
+ }
123
+ /** Ask the agent to stop the turn it is on. The sandbox stays up. */
124
+ async interrupt() {
125
+ await this.http.request("POST", `/api/conversations/${this.id}/interrupt`);
126
+ }
127
+ /** Tear the sandbox down. Nothing resumes after this. */
128
+ async terminate() {
129
+ await this.http.request("POST", `/api/conversations/${this.id}/terminate`);
130
+ }
131
+ /** Delete the conversation and its history. */
132
+ async delete() {
133
+ await this.http.request("DELETE", `/api/conversations/${this.id}`);
134
+ }
135
+ /**
136
+ * The raw log feed, reconnecting on its own. Everything, from `after`
137
+ * onwards, of every turn — `run`/`send` is the filtered view of this.
138
+ */
139
+ events(options = {}) {
140
+ return streamEvents(this.http, this.id, options);
141
+ }
142
+ /** One page of the log feed as JSON, for a client that would rather poll. */
143
+ async eventPage(after = 0, limit = 1000) {
144
+ const out = await this.http.request("GET", `/api/conversations/${this.id}/events`, { query: { after, limit, blocks: "true" } });
145
+ const meta = out?.meta ?? {};
146
+ return {
147
+ events: out?.data ?? [],
148
+ nextCursor: typeof meta.next_cursor === "number" ? meta.next_cursor : after,
149
+ hasMore: Boolean(meta.has_more),
150
+ };
151
+ }
152
+ /** The highest turn number so far; the next prompt is this plus one. */
153
+ async lastTurnNumber() {
154
+ const turns = await this.turns();
155
+ return turns.reduce((max, turn) => Math.max(max, Number(turn.turn_number) || 0), 0);
156
+ }
157
+ /** Where this handle has read to, discovering it if nobody has looked yet. */
158
+ async cursor() {
159
+ return this.cursorValue > 0 ? this.cursorValue : this.discoverCursor();
160
+ }
161
+ /**
162
+ * Find the end of the log feed cheaply.
163
+ *
164
+ * A cold `resume().send()` has no cursor, and starting from 0 would replay
165
+ * every event of every earlier turn before reaching ours. Draining the
166
+ * `stage` stream (`wait=false`) is a few rows for even a long conversation,
167
+ * and its ids are the same global ids the full feed uses.
168
+ */
169
+ async discoverCursor() {
170
+ let last = 0;
171
+ try {
172
+ for await (const event of streamEvents(this.http, this.id, {
173
+ streams: "stage",
174
+ wait: false,
175
+ maxRetries: 0,
176
+ })) {
177
+ if (typeof event.id === "number" && event.id > last)
178
+ last = event.id;
179
+ }
180
+ }
181
+ catch {
182
+ // Not worth failing a send over; 0 replays, which is correct if noisy.
183
+ return 0;
184
+ }
185
+ this.cursorValue = last;
186
+ return last;
187
+ }
188
+ }
189
+ //# sourceMappingURL=conversation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation.js","sourceRoot":"","sources":["../src/conversation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Failures, keyed the way callers actually branch on them.
3
+ *
4
+ * The apps built on Fountain switch on the server's `error` string, not on the
5
+ * status: `conversation_busy` is a 400, `sandbox_quota_exceeded` is a 429 and
6
+ * `provisioning` is a 503, and what a UI wants to say about each has nothing
7
+ * to do with those numbers. So `code` is the primary axis here, status the
8
+ * secondary one, and the conditions worth retrying say so themselves.
9
+ */
10
+ /** The `error` strings the API sends. Open — a new one is a string, not a break. */
11
+ export type FountainErrorCode = "conversation_busy" | "provisioning" | "sprite_probe_failed" | "sandbox_quota_exceeded" | "sandbox_at_capacity" | "sandbox_not_found" | "sandbox_not_attachable" | "sandbox_identity_mismatch" | "sandbox_runtime_mismatch" | "insufficient_credits" | "fleet_full" | "subscription_required" | "rate_limited" | "account_suspended" | "environment_not_allowed" | "environment_not_found" | "vault_not_allowed" | "parent_conversation_not_found" | "not_found" | "unauthorized" | (string & {});
12
+ export interface FountainErrorInit {
13
+ status?: number;
14
+ code?: FountainErrorCode;
15
+ body?: unknown;
16
+ /** Seconds the server asked us to wait, from `Retry-After`. */
17
+ retryAfter?: number | null;
18
+ cause?: unknown;
19
+ }
20
+ export declare class FountainError extends Error {
21
+ /** HTTP status, or 0 for a transport-level failure. */
22
+ readonly status: number;
23
+ /** The API's `error` field when it sent one. */
24
+ readonly code: FountainErrorCode | undefined;
25
+ /** The parsed response body, whatever shape it had. */
26
+ readonly body: unknown;
27
+ /** Seconds to wait before retrying, when the server said. */
28
+ readonly retryAfter: number | null;
29
+ constructor(message: string, init?: FountainErrorInit);
30
+ /**
31
+ * Whether trying the same call again could work. True for a busy
32
+ * conversation, a sandbox still coming up, a rate limit and 5xx — false for
33
+ * anything the caller has to change first.
34
+ */
35
+ get retryable(): boolean;
36
+ /** `errors: {field: [msg, …]}` as the server sends a 422; empty otherwise. */
37
+ get fieldErrors(): Record<string, string[]>;
38
+ }
39
+ /** No API key, or the key was rejected (401). */
40
+ export declare class AuthError extends FountainError {
41
+ }
42
+ /**
43
+ * The account is out of credit (`insufficient_credits`, 402). `upgradeUrl` is
44
+ * the billing page, where credit is bought. The class keeps its old name so
45
+ * callers written against the subscription era still catch it.
46
+ */
47
+ export declare class SubscriptionRequiredError extends FountainError {
48
+ get upgradeUrl(): string | undefined;
49
+ }
50
+ /** Wrong id, or it belongs to another account — Fountain does not distinguish (404). */
51
+ export declare class NotFoundError extends FountainError {
52
+ }
53
+ /** The request was well-formed but rejected (422). Read `fieldErrors`. */
54
+ export declare class ValidationError extends FountainError {
55
+ }
56
+ /** Too many requests (429). */
57
+ export declare class RateLimitError extends FountainError {
58
+ }
59
+ /**
60
+ * The agent is still working on the previous prompt (`conversation_busy`, a
61
+ * 400). Wait for the turn in flight, then send again — `Conversation#send`
62
+ * with `waitForIdle` does that for you.
63
+ */
64
+ export declare class ConversationBusyError extends FountainError {
65
+ }
66
+ /**
67
+ * The sandbox is not up yet, Fountain could not reach the provider to check
68
+ * (`provisioning` / `sprite_probe_failed`), or the deployment is at its fleet
69
+ * ceiling (`fleet_full`) — all 503 with a `Retry-After`. Nothing is wrong and
70
+ * nothing was changed; the same call will work shortly.
71
+ */
72
+ export declare class NotReadyError extends FountainError {
73
+ }
74
+ /**
75
+ * The account is at its concurrent-sandbox cap (`sandbox_quota_exceeded`, 429).
76
+ * Deliberately not a billing error: terminate a conversation and continue.
77
+ */
78
+ export declare class QuotaExceededError extends FountainError {
79
+ /** Sandboxes in use right now. */
80
+ get activeSandboxes(): number | undefined;
81
+ /** The cap. */
82
+ get limit(): number | undefined;
83
+ }
84
+ /** `run`/`send` gave up waiting. The turn is still running — resume it. */
85
+ export declare class TimeoutError extends FountainError {
86
+ readonly conversationId: string;
87
+ /** Whatever the agent had said by the deadline. */
88
+ readonly partialText: string;
89
+ constructor(message: string, conversationId: string, partialText: string);
90
+ }
91
+ /** A name that matched no agent/vault/environment, or matched more than one. */
92
+ export declare class ResolutionError extends FountainError {
93
+ }
94
+ /**
95
+ * The request never reached Fountain. In a browser this is nearly always CORS
96
+ * or a wrong base URL, and the message says so, because "Failed to fetch" has
97
+ * sent more than one person hunting through their own code.
98
+ */
99
+ export declare class ConnectionError extends FountainError {
100
+ }
101
+ /** Pick the class for a failure. `code` decides; status is the fallback. */
102
+ export declare function errorForStatus(status: number, body: unknown, method: string, url: string, headers?: Headers): FountainError;
package/dist/errors.js ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Failures, keyed the way callers actually branch on them.
3
+ *
4
+ * The apps built on Fountain switch on the server's `error` string, not on the
5
+ * status: `conversation_busy` is a 400, `sandbox_quota_exceeded` is a 429 and
6
+ * `provisioning` is a 503, and what a UI wants to say about each has nothing
7
+ * to do with those numbers. So `code` is the primary axis here, status the
8
+ * secondary one, and the conditions worth retrying say so themselves.
9
+ */
10
+ export class FountainError extends Error {
11
+ /** HTTP status, or 0 for a transport-level failure. */
12
+ status;
13
+ /** The API's `error` field when it sent one. */
14
+ code;
15
+ /** The parsed response body, whatever shape it had. */
16
+ body;
17
+ /** Seconds to wait before retrying, when the server said. */
18
+ retryAfter;
19
+ constructor(message, init = {}) {
20
+ super(message, init.cause !== undefined ? { cause: init.cause } : undefined);
21
+ this.name = new.target.name;
22
+ this.status = init.status ?? 0;
23
+ this.code = init.code;
24
+ this.body = init.body;
25
+ this.retryAfter = init.retryAfter ?? null;
26
+ }
27
+ /**
28
+ * Whether trying the same call again could work. True for a busy
29
+ * conversation, a sandbox still coming up, a rate limit and 5xx — false for
30
+ * anything the caller has to change first.
31
+ */
32
+ get retryable() {
33
+ if (RETRYABLE_CODES.has(this.code ?? ""))
34
+ return true;
35
+ return this.status === 429 || (this.status >= 500 && this.status < 600);
36
+ }
37
+ /** `errors: {field: [msg, …]}` as the server sends a 422; empty otherwise. */
38
+ get fieldErrors() {
39
+ const errors = this.body?.errors;
40
+ if (!errors || typeof errors !== "object" || Array.isArray(errors))
41
+ return {};
42
+ const out = {};
43
+ for (const [field, value] of Object.entries(errors)) {
44
+ if (Array.isArray(value))
45
+ out[field] = value.filter((v) => typeof v === "string");
46
+ else if (typeof value === "string")
47
+ out[field] = [value];
48
+ }
49
+ return out;
50
+ }
51
+ }
52
+ const RETRYABLE_CODES = new Set([
53
+ "conversation_busy",
54
+ "provisioning",
55
+ "sprite_probe_failed",
56
+ "sandbox_quota_exceeded",
57
+ // Another conversation's turn is using a one-at-a-time runtime's sandbox;
58
+ // it clears when that turn ends.
59
+ "sandbox_at_capacity",
60
+ "rate_limited",
61
+ ]);
62
+ /** No API key, or the key was rejected (401). */
63
+ export class AuthError extends FountainError {
64
+ }
65
+ /**
66
+ * The account is out of credit (`insufficient_credits`, 402). `upgradeUrl` is
67
+ * the billing page, where credit is bought. The class keeps its old name so
68
+ * callers written against the subscription era still catch it.
69
+ */
70
+ export class SubscriptionRequiredError extends FountainError {
71
+ get upgradeUrl() {
72
+ const url = this.body?.upgrade_url;
73
+ return typeof url === "string" ? url : undefined;
74
+ }
75
+ }
76
+ /** Wrong id, or it belongs to another account — Fountain does not distinguish (404). */
77
+ export class NotFoundError extends FountainError {
78
+ }
79
+ /** The request was well-formed but rejected (422). Read `fieldErrors`. */
80
+ export class ValidationError extends FountainError {
81
+ }
82
+ /** Too many requests (429). */
83
+ export class RateLimitError extends FountainError {
84
+ }
85
+ /**
86
+ * The agent is still working on the previous prompt (`conversation_busy`, a
87
+ * 400). Wait for the turn in flight, then send again — `Conversation#send`
88
+ * with `waitForIdle` does that for you.
89
+ */
90
+ export class ConversationBusyError extends FountainError {
91
+ }
92
+ /**
93
+ * The sandbox is not up yet, Fountain could not reach the provider to check
94
+ * (`provisioning` / `sprite_probe_failed`), or the deployment is at its fleet
95
+ * ceiling (`fleet_full`) — all 503 with a `Retry-After`. Nothing is wrong and
96
+ * nothing was changed; the same call will work shortly.
97
+ */
98
+ export class NotReadyError extends FountainError {
99
+ }
100
+ /**
101
+ * The account is at its concurrent-sandbox cap (`sandbox_quota_exceeded`, 429).
102
+ * Deliberately not a billing error: terminate a conversation and continue.
103
+ */
104
+ export class QuotaExceededError extends FountainError {
105
+ /** Sandboxes in use right now. */
106
+ get activeSandboxes() {
107
+ return numberFrom(this.body, "active_sandboxes");
108
+ }
109
+ /** The cap. */
110
+ get limit() {
111
+ return numberFrom(this.body, "limit");
112
+ }
113
+ }
114
+ /** `run`/`send` gave up waiting. The turn is still running — resume it. */
115
+ export class TimeoutError extends FountainError {
116
+ conversationId;
117
+ /** Whatever the agent had said by the deadline. */
118
+ partialText;
119
+ constructor(message, conversationId, partialText) {
120
+ super(message);
121
+ this.conversationId = conversationId;
122
+ this.partialText = partialText;
123
+ }
124
+ }
125
+ /** A name that matched no agent/vault/environment, or matched more than one. */
126
+ export class ResolutionError extends FountainError {
127
+ }
128
+ /**
129
+ * The request never reached Fountain. In a browser this is nearly always CORS
130
+ * or a wrong base URL, and the message says so, because "Failed to fetch" has
131
+ * sent more than one person hunting through their own code.
132
+ */
133
+ export class ConnectionError extends FountainError {
134
+ }
135
+ const HINTS = {
136
+ 400: "bad request",
137
+ 401: "unauthorized — check the API key",
138
+ 402: "payment required — the account is out of credit",
139
+ 403: "forbidden — the key may lack the scope for this call",
140
+ 404: "not found — wrong id, or it belongs to another account",
141
+ 409: "conflict",
142
+ 422: "rejected",
143
+ 429: "rate limited",
144
+ 503: "temporarily unavailable",
145
+ };
146
+ /** Pick the class for a failure. `code` decides; status is the fallback. */
147
+ export function errorForStatus(status, body, method, url, headers) {
148
+ const record = body && typeof body === "object" ? body : null;
149
+ const code = typeof record?.error === "string" ? record.error : undefined;
150
+ let detail = "";
151
+ if (record) {
152
+ const message = record.message ?? record.error ?? record.errors;
153
+ detail = typeof message === "string" ? message : message === undefined ? "" : JSON.stringify(message);
154
+ }
155
+ else if (typeof body === "string" && body.trim()) {
156
+ detail = body.trim().slice(0, 300);
157
+ }
158
+ const retryHeader = headers?.get("retry-after");
159
+ const retryAfter = retryHeader && Number.isFinite(Number(retryHeader)) ? Number(retryHeader) : null;
160
+ const message = [`HTTP ${status}`, HINTS[status], detail, `(${method} ${url})`]
161
+ .filter(Boolean)
162
+ .join(" ");
163
+ const init = { status, code, body, retryAfter };
164
+ switch (code) {
165
+ case "conversation_busy":
166
+ return new ConversationBusyError(message, init);
167
+ case "provisioning":
168
+ case "sprite_probe_failed":
169
+ return new NotReadyError(message, init);
170
+ case "sandbox_quota_exceeded":
171
+ return new QuotaExceededError(message, init);
172
+ case "subscription_required":
173
+ case "insufficient_credits":
174
+ return new SubscriptionRequiredError(message, init);
175
+ case "fleet_full":
176
+ return new NotReadyError(message, init);
177
+ }
178
+ switch (status) {
179
+ case 401:
180
+ return new AuthError(message, init);
181
+ case 402:
182
+ return new SubscriptionRequiredError(message, init);
183
+ case 404:
184
+ return new NotFoundError(message, init);
185
+ case 422:
186
+ return new ValidationError(message, init);
187
+ case 429:
188
+ return new RateLimitError(message, init);
189
+ default:
190
+ return new FountainError(message, init);
191
+ }
192
+ }
193
+ function numberFrom(body, key) {
194
+ const value = body?.[key];
195
+ return typeof value === "number" ? value : undefined;
196
+ }
197
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}