@burtson-labs/bandit-engine 2.0.40 → 2.0.42
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/README.md +14 -11
- package/dist/{aiProviderStore-JMA5RWX7.mjs → aiProviderStore-UQI33C5E.mjs} +2 -2
- package/dist/{chat-JMWPOSQ4.mjs → chat-XDC4SNJF.mjs} +5 -5
- package/dist/chat-provider.js +156 -106
- package/dist/chat-provider.js.map +1 -1
- package/dist/chat-provider.mjs +4 -4
- package/dist/{chunk-7KEBNVCO.mjs → chunk-4RCAVVDN.mjs} +15 -10
- package/dist/{chunk-7KEBNVCO.mjs.map → chunk-4RCAVVDN.mjs.map} +1 -1
- package/dist/{chunk-QJYPWWA5.mjs → chunk-54ZQ3FSN.mjs} +104 -61
- package/dist/chunk-54ZQ3FSN.mjs.map +1 -0
- package/dist/{chunk-26QQ4CLA.mjs → chunk-EOEI74X4.mjs} +4 -4
- package/dist/{chunk-6ELNWXKC.mjs → chunk-ERV7GLY3.mjs} +4 -4
- package/dist/{chunk-75W5VWPV.mjs → chunk-H4PBQ5LJ.mjs} +51 -46
- package/dist/chunk-H4PBQ5LJ.mjs.map +1 -0
- package/dist/{chunk-D3AGKOM6.mjs → chunk-KBKWVG7X.mjs} +3 -3
- package/dist/{chunk-VIYBZO5W.mjs → chunk-SBNENBUQ.mjs} +3 -3
- package/dist/{chunk-2ZCR2TDY.mjs → chunk-UXE67LR7.mjs} +8 -6
- package/dist/{chunk-2ZCR2TDY.mjs.map → chunk-UXE67LR7.mjs.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +164 -109
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -8
- package/dist/management/management.js +164 -109
- package/dist/management/management.js.map +1 -1
- package/dist/management/management.mjs +6 -6
- package/dist/modals/chat-modal/chat-modal.js +108 -63
- package/dist/modals/chat-modal/chat-modal.js.map +1 -1
- package/dist/modals/chat-modal/chat-modal.mjs +4 -4
- package/docs/01_quickstart.md +10 -4
- package/docs/02_gateway_api.md +19 -3
- package/docs/03_provider_integration.md +5 -4
- package/docs/api_reference/media/02_gateway_api.md +19 -3
- package/docs/api_reference/media/README.md +3 -1
- package/package.json +1 -1
- package/dist/chunk-75W5VWPV.mjs.map +0 -1
- package/dist/chunk-QJYPWWA5.mjs.map +0 -1
- /package/dist/{aiProviderStore-JMA5RWX7.mjs.map → aiProviderStore-UQI33C5E.mjs.map} +0 -0
- /package/dist/{chat-JMWPOSQ4.mjs.map → chat-XDC4SNJF.mjs.map} +0 -0
- /package/dist/{chunk-26QQ4CLA.mjs.map → chunk-EOEI74X4.mjs.map} +0 -0
- /package/dist/{chunk-6ELNWXKC.mjs.map → chunk-ERV7GLY3.mjs.map} +0 -0
- /package/dist/{chunk-D3AGKOM6.mjs.map → chunk-KBKWVG7X.mjs.map} +0 -0
- /package/dist/{chunk-VIYBZO5W.mjs.map → chunk-SBNENBUQ.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -4468,61 +4468,15 @@ var init_gateway_service = __esm({
|
|
|
4468
4468
|
*/
|
|
4469
4469
|
chat(request) {
|
|
4470
4470
|
const endpoint = request.provider === "ollama" ? `/api/${request.provider}/chat` : request.provider ? `/api/${request.provider}/chat/completions` : "/api/chat/completions";
|
|
4471
|
-
const
|
|
4471
|
+
const fallbackEndpoint = request.provider === "bandit" ? "/completions" : null;
|
|
4472
4472
|
const normalizedModel = request.provider === "bandit" ? (() => {
|
|
4473
4473
|
const trimmed = (request.model ?? "").replace(/^bandit:/, "").trim();
|
|
4474
4474
|
return trimmed !== "" ? trimmed : "bandit-core-1";
|
|
4475
4475
|
})() : request.model;
|
|
4476
|
-
debugLogger.debug(`Gateway chat request to ${url} with provider: ${request.provider || "default"}`, {
|
|
4477
|
-
model: normalizedModel,
|
|
4478
|
-
messageCount: request.messages.length,
|
|
4479
|
-
hasImages: !!(request.images && request.images.length > 0),
|
|
4480
|
-
imageCount: request.images?.length || 0
|
|
4481
|
-
});
|
|
4482
4476
|
const requestBody = { ...request, model: normalizedModel, stream: request.stream !== false };
|
|
4483
4477
|
return new import_rxjs6.Observable((observer) => {
|
|
4484
4478
|
const controller = new AbortController();
|
|
4485
|
-
const
|
|
4486
|
-
method: "POST",
|
|
4487
|
-
headers: this._getHeaders(),
|
|
4488
|
-
body: JSON.stringify(requestBody),
|
|
4489
|
-
signal: controller.signal
|
|
4490
|
-
});
|
|
4491
|
-
task.then(async (response) => {
|
|
4492
|
-
debugLogger.debug(`Gateway chat response status: ${response.status} for provider: ${request.provider || "default"}`);
|
|
4493
|
-
if (!response.ok) {
|
|
4494
|
-
let errorText = "";
|
|
4495
|
-
let errorData = null;
|
|
4496
|
-
try {
|
|
4497
|
-
errorText = await response.text();
|
|
4498
|
-
debugLogger.error("GatewayService chat error response body", {
|
|
4499
|
-
status: response.status,
|
|
4500
|
-
statusText: response.statusText,
|
|
4501
|
-
url: response.url,
|
|
4502
|
-
body: errorText
|
|
4503
|
-
});
|
|
4504
|
-
} catch (readError) {
|
|
4505
|
-
debugLogger.error("GatewayService chat failed to read error response body", { error: readError });
|
|
4506
|
-
errorText = `Request failed with status ${response.status}`;
|
|
4507
|
-
}
|
|
4508
|
-
try {
|
|
4509
|
-
errorData = JSON.parse(errorText);
|
|
4510
|
-
debugLogger.error("GatewayService chat parsed error payload", errorData);
|
|
4511
|
-
} catch (parseError) {
|
|
4512
|
-
debugLogger.error("GatewayService chat error payload was not valid JSON");
|
|
4513
|
-
errorData = { message: errorText };
|
|
4514
|
-
}
|
|
4515
|
-
const error = this._createHttpError(
|
|
4516
|
-
`POST ${url} failed: ${response.status} ${response.statusText ?? ""}`,
|
|
4517
|
-
{
|
|
4518
|
-
status: response.status,
|
|
4519
|
-
statusText: response.statusText ?? "",
|
|
4520
|
-
data: errorData,
|
|
4521
|
-
url
|
|
4522
|
-
}
|
|
4523
|
-
);
|
|
4524
|
-
throw error;
|
|
4525
|
-
}
|
|
4479
|
+
const handleStreamingResponse = async (response) => {
|
|
4526
4480
|
const reader = response.body?.getReader();
|
|
4527
4481
|
const decoder = new TextDecoder();
|
|
4528
4482
|
let buffer = "";
|
|
@@ -4597,14 +4551,75 @@ var init_gateway_service = __esm({
|
|
|
4597
4551
|
}).catch((err) => observer.error(err));
|
|
4598
4552
|
};
|
|
4599
4553
|
read();
|
|
4600
|
-
}
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4554
|
+
};
|
|
4555
|
+
const sendRequest = (targetEndpoint, allowFallback) => {
|
|
4556
|
+
const url = `${this._baseUrl}${targetEndpoint}`;
|
|
4557
|
+
debugLogger.debug(`Gateway chat request to ${url} with provider: ${request.provider || "default"}`, {
|
|
4558
|
+
model: normalizedModel,
|
|
4559
|
+
messageCount: request.messages.length,
|
|
4560
|
+
hasImages: !!(request.images && request.images.length > 0),
|
|
4561
|
+
imageCount: request.images?.length || 0
|
|
4605
4562
|
});
|
|
4606
|
-
|
|
4607
|
-
|
|
4563
|
+
fetch(url, {
|
|
4564
|
+
method: "POST",
|
|
4565
|
+
headers: this._getHeaders(),
|
|
4566
|
+
body: JSON.stringify(requestBody),
|
|
4567
|
+
signal: controller.signal
|
|
4568
|
+
}).then(async (response) => {
|
|
4569
|
+
debugLogger.debug(`Gateway chat response status: ${response.status} for provider: ${request.provider || "default"}`);
|
|
4570
|
+
if (response.status === 404 && allowFallback && fallbackEndpoint) {
|
|
4571
|
+
debugLogger.warn("GatewayService chat endpoint returned 404, attempting fallback route", {
|
|
4572
|
+
provider: request.provider,
|
|
4573
|
+
attemptedEndpoint: targetEndpoint,
|
|
4574
|
+
fallbackEndpoint
|
|
4575
|
+
});
|
|
4576
|
+
sendRequest(fallbackEndpoint, false);
|
|
4577
|
+
return;
|
|
4578
|
+
}
|
|
4579
|
+
if (!response.ok) {
|
|
4580
|
+
let errorText = "";
|
|
4581
|
+
let errorData = null;
|
|
4582
|
+
try {
|
|
4583
|
+
errorText = await response.text();
|
|
4584
|
+
debugLogger.error("GatewayService chat error response body", {
|
|
4585
|
+
status: response.status,
|
|
4586
|
+
statusText: response.statusText,
|
|
4587
|
+
url: response.url,
|
|
4588
|
+
body: errorText
|
|
4589
|
+
});
|
|
4590
|
+
} catch (readError) {
|
|
4591
|
+
debugLogger.error("GatewayService chat failed to read error response body", { error: readError });
|
|
4592
|
+
errorText = `Request failed with status ${response.status}`;
|
|
4593
|
+
}
|
|
4594
|
+
try {
|
|
4595
|
+
errorData = JSON.parse(errorText);
|
|
4596
|
+
debugLogger.error("GatewayService chat parsed error payload", errorData);
|
|
4597
|
+
} catch (parseError) {
|
|
4598
|
+
debugLogger.error("GatewayService chat error payload was not valid JSON");
|
|
4599
|
+
errorData = { message: errorText };
|
|
4600
|
+
}
|
|
4601
|
+
const error = this._createHttpError(
|
|
4602
|
+
`POST ${url} failed: ${response.status} ${response.statusText ?? ""}`,
|
|
4603
|
+
{
|
|
4604
|
+
status: response.status,
|
|
4605
|
+
statusText: response.statusText ?? "",
|
|
4606
|
+
data: errorData,
|
|
4607
|
+
url
|
|
4608
|
+
}
|
|
4609
|
+
);
|
|
4610
|
+
throw error;
|
|
4611
|
+
}
|
|
4612
|
+
await handleStreamingResponse(response);
|
|
4613
|
+
}).catch((err) => {
|
|
4614
|
+
debugLogger.error("GatewayService chat fetch error", {
|
|
4615
|
+
error: err,
|
|
4616
|
+
url,
|
|
4617
|
+
provider: request.provider
|
|
4618
|
+
});
|
|
4619
|
+
observer.error(err);
|
|
4620
|
+
});
|
|
4621
|
+
};
|
|
4622
|
+
sendRequest(endpoint, true);
|
|
4608
4623
|
return () => {
|
|
4609
4624
|
try {
|
|
4610
4625
|
controller.abort();
|
|
@@ -4737,18 +4752,46 @@ var init_gateway_service = __esm({
|
|
|
4737
4752
|
);
|
|
4738
4753
|
}
|
|
4739
4754
|
_getHeaders() {
|
|
4740
|
-
const
|
|
4755
|
+
const rawToken2 = this._tokenFactory();
|
|
4741
4756
|
const headers = {
|
|
4742
4757
|
"Content-Type": "application/json"
|
|
4743
4758
|
};
|
|
4744
|
-
if (
|
|
4745
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
4746
|
-
debugLogger.debug("Authorization header set with token");
|
|
4747
|
-
} else {
|
|
4759
|
+
if (!rawToken2) {
|
|
4748
4760
|
debugLogger.warn("GatewayService: No token found, skipping Authorization header");
|
|
4761
|
+
return headers;
|
|
4762
|
+
}
|
|
4763
|
+
const token = rawToken2.trim();
|
|
4764
|
+
if (token === "") {
|
|
4765
|
+
debugLogger.warn("GatewayService: Token factory returned empty string");
|
|
4766
|
+
return headers;
|
|
4767
|
+
}
|
|
4768
|
+
if (/^(Bearer|ApiKey)\s+/i.test(token)) {
|
|
4769
|
+
headers["Authorization"] = token;
|
|
4770
|
+
debugLogger.debug("GatewayService: Authorization header set with explicit scheme");
|
|
4771
|
+
return headers;
|
|
4772
|
+
}
|
|
4773
|
+
if (this._isLikelyBanditApiKey(token)) {
|
|
4774
|
+
headers["Authorization"] = `ApiKey ${token}`;
|
|
4775
|
+
headers["X-Burtson-Api-Key"] = token;
|
|
4776
|
+
debugLogger.debug("GatewayService: Authorization header set using API key");
|
|
4777
|
+
return headers;
|
|
4749
4778
|
}
|
|
4779
|
+
if (this._isLikelyJwt(token)) {
|
|
4780
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
4781
|
+
debugLogger.debug("GatewayService: Authorization header set using bearer token");
|
|
4782
|
+
return headers;
|
|
4783
|
+
}
|
|
4784
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
4785
|
+
debugLogger.debug("GatewayService: Authorization header defaulted to bearer scheme");
|
|
4750
4786
|
return headers;
|
|
4751
4787
|
}
|
|
4788
|
+
_isLikelyJwt(token) {
|
|
4789
|
+
const segments = token.split(".");
|
|
4790
|
+
return segments.length === 3 && segments.every((segment) => segment.length > 0);
|
|
4791
|
+
}
|
|
4792
|
+
_isLikelyBanditApiKey(value) {
|
|
4793
|
+
return /^bai_[a-z0-9]{10,}$/i.test(value);
|
|
4794
|
+
}
|
|
4752
4795
|
/**
|
|
4753
4796
|
* Submit feedback to the gateway API
|
|
4754
4797
|
*/
|
|
@@ -9435,9 +9478,11 @@ var init_useVectorStore = __esm({
|
|
|
9435
9478
|
setHasCompatibleProvider(false);
|
|
9436
9479
|
return false;
|
|
9437
9480
|
}
|
|
9438
|
-
const
|
|
9439
|
-
const
|
|
9440
|
-
const
|
|
9481
|
+
const vectorCapableProviders = /* @__PURE__ */ new Set(["ollama", "bandit"]);
|
|
9482
|
+
const isNativeOllama = config.type === "ollama";
|
|
9483
|
+
const hasVectorCapableProvider = typeof config.provider === "string" && vectorCapableProviders.has(config.provider);
|
|
9484
|
+
const isGatewayWithVectorSupport = config.type === "gateway" && hasVectorCapableProvider;
|
|
9485
|
+
const isCompatible = isNativeOllama || isGatewayWithVectorSupport;
|
|
9441
9486
|
setHasCompatibleProvider(isCompatible);
|
|
9442
9487
|
return isCompatible;
|
|
9443
9488
|
} catch (error) {
|
|
@@ -29268,26 +29313,7 @@ var AIProviderInitService = class _AIProviderInitService {
|
|
|
29268
29313
|
if (providerConfig.type === "anthropic" /* ANTHROPIC */) {
|
|
29269
29314
|
providerConfig = this.convertAnthropicConfig(providerConfig, settings?.gatewayApiUrl);
|
|
29270
29315
|
}
|
|
29271
|
-
|
|
29272
|
-
providerConfig.tokenFactory = () => {
|
|
29273
|
-
let token = authenticationService.getToken();
|
|
29274
|
-
if (!token) {
|
|
29275
|
-
token = localStorage.getItem("authToken");
|
|
29276
|
-
}
|
|
29277
|
-
if (!token) {
|
|
29278
|
-
try {
|
|
29279
|
-
const { useAuthenticationStore: useAuthenticationStore2 } = require("../../store/authenticationStore");
|
|
29280
|
-
const authStore = useAuthenticationStore2.getState();
|
|
29281
|
-
token = authStore.token;
|
|
29282
|
-
} catch (e) {
|
|
29283
|
-
}
|
|
29284
|
-
}
|
|
29285
|
-
debugLogger.info("AI Provider Init: IndexedDB config token factory", {
|
|
29286
|
-
hasToken: !!token
|
|
29287
|
-
});
|
|
29288
|
-
return token;
|
|
29289
|
-
};
|
|
29290
|
-
}
|
|
29316
|
+
providerConfig = this.ensureTokenFactory(providerConfig);
|
|
29291
29317
|
try {
|
|
29292
29318
|
const { createProvider } = useAIProviderStore.getState();
|
|
29293
29319
|
createProvider(providerConfig);
|
|
@@ -29315,27 +29341,7 @@ var AIProviderInitService = class _AIProviderInitService {
|
|
|
29315
29341
|
if (providerConfig.type === "anthropic" /* ANTHROPIC */) {
|
|
29316
29342
|
providerConfig = this.convertAnthropicConfig(providerConfig, settings.gatewayApiUrl);
|
|
29317
29343
|
}
|
|
29318
|
-
|
|
29319
|
-
providerConfig.tokenFactory = () => {
|
|
29320
|
-
let token = authenticationService.getToken();
|
|
29321
|
-
if (!token) {
|
|
29322
|
-
token = localStorage.getItem("authToken");
|
|
29323
|
-
}
|
|
29324
|
-
if (!token) {
|
|
29325
|
-
try {
|
|
29326
|
-
const { useAuthenticationStore: useAuthenticationStore2 } = require("../../store/authenticationStore");
|
|
29327
|
-
const authStore = useAuthenticationStore2.getState();
|
|
29328
|
-
token = authStore.token;
|
|
29329
|
-
} catch (e) {
|
|
29330
|
-
}
|
|
29331
|
-
}
|
|
29332
|
-
debugLogger.info("AIProviderInit: Explicit config tokenFactory", {
|
|
29333
|
-
hasToken: !!token,
|
|
29334
|
-
localStorage: !!localStorage.getItem("authToken")
|
|
29335
|
-
});
|
|
29336
|
-
return token;
|
|
29337
|
-
};
|
|
29338
|
-
}
|
|
29344
|
+
providerConfig = this.ensureTokenFactory(providerConfig);
|
|
29339
29345
|
debugLogger.info("Using explicit AI provider config", providerConfig);
|
|
29340
29346
|
} else {
|
|
29341
29347
|
providerConfig = {
|
|
@@ -29447,9 +29453,10 @@ var AIProviderInitService = class _AIProviderInitService {
|
|
|
29447
29453
|
*/
|
|
29448
29454
|
switchProvider(config) {
|
|
29449
29455
|
try {
|
|
29456
|
+
const normalizedConfig = this.ensureTokenFactory({ ...config });
|
|
29450
29457
|
const { switchProvider } = useAIProviderStore.getState();
|
|
29451
|
-
switchProvider(
|
|
29452
|
-
debugLogger.info(`Switched to AI provider: ${
|
|
29458
|
+
switchProvider(normalizedConfig);
|
|
29459
|
+
debugLogger.info(`Switched to AI provider: ${normalizedConfig.type}`);
|
|
29453
29460
|
} catch (error) {
|
|
29454
29461
|
debugLogger.error("Failed to switch AI provider:", { error });
|
|
29455
29462
|
throw error;
|
|
@@ -29483,6 +29490,49 @@ var AIProviderInitService = class _AIProviderInitService {
|
|
|
29483
29490
|
debugLogger.info("AI Provider Init: Converted direct Anthropic provider to gateway configuration");
|
|
29484
29491
|
return normalized;
|
|
29485
29492
|
}
|
|
29493
|
+
/**
|
|
29494
|
+
* Ensure providers that require auth have a token factory configured.
|
|
29495
|
+
* Handles both UI auth tokens and API key scenarios.
|
|
29496
|
+
*/
|
|
29497
|
+
ensureTokenFactory(config) {
|
|
29498
|
+
if (config.type === "ollama" /* OLLAMA */ || config.type === "gateway" /* GATEWAY */) {
|
|
29499
|
+
const existingFactory = config.tokenFactory;
|
|
29500
|
+
if (existingFactory) {
|
|
29501
|
+
return config;
|
|
29502
|
+
}
|
|
29503
|
+
if (typeof config.apiKey === "string" && config.apiKey.trim() !== "") {
|
|
29504
|
+
const key = config.apiKey.trim();
|
|
29505
|
+
config.tokenFactory = () => key;
|
|
29506
|
+
debugLogger.info("AIProviderInit: Using API key for token factory", {
|
|
29507
|
+
type: config.type,
|
|
29508
|
+
hasKey: true
|
|
29509
|
+
});
|
|
29510
|
+
return config;
|
|
29511
|
+
}
|
|
29512
|
+
config.tokenFactory = () => {
|
|
29513
|
+
let token = authenticationService.getToken();
|
|
29514
|
+
if (!token && typeof localStorage !== "undefined") {
|
|
29515
|
+
try {
|
|
29516
|
+
token = localStorage.getItem("authToken");
|
|
29517
|
+
} catch {
|
|
29518
|
+
}
|
|
29519
|
+
}
|
|
29520
|
+
if (!token) {
|
|
29521
|
+
try {
|
|
29522
|
+
const { useAuthenticationStore: useAuthenticationStore2 } = require("../../store/authenticationStore");
|
|
29523
|
+
const authStore = useAuthenticationStore2.getState();
|
|
29524
|
+
token = authStore.token;
|
|
29525
|
+
} catch {
|
|
29526
|
+
}
|
|
29527
|
+
}
|
|
29528
|
+
debugLogger.info("AIProviderInit: Token factory resolved auth token", {
|
|
29529
|
+
hasToken: !!token
|
|
29530
|
+
});
|
|
29531
|
+
return token;
|
|
29532
|
+
};
|
|
29533
|
+
}
|
|
29534
|
+
return config;
|
|
29535
|
+
}
|
|
29486
29536
|
};
|
|
29487
29537
|
var aiProviderInitService = AIProviderInitService.getInstance();
|
|
29488
29538
|
|
|
@@ -39186,11 +39236,16 @@ var ProviderTab = () => {
|
|
|
39186
39236
|
const trimmed = sanitized.defaultModel.trim();
|
|
39187
39237
|
sanitized.defaultModel = trimmed || void 0;
|
|
39188
39238
|
}
|
|
39239
|
+
if (typeof sanitized.apiKey === "string") {
|
|
39240
|
+
const trimmedKey = sanitized.apiKey.trim();
|
|
39241
|
+
sanitized.apiKey = trimmedKey || void 0;
|
|
39242
|
+
}
|
|
39189
39243
|
return sanitized;
|
|
39190
39244
|
}, []);
|
|
39191
39245
|
const [providerConfig, setProviderConfig] = (0, import_react53.useState)({
|
|
39192
|
-
type: "
|
|
39193
|
-
|
|
39246
|
+
type: "gateway",
|
|
39247
|
+
gatewayUrl: packageSettings?.gatewayApiUrl || "",
|
|
39248
|
+
provider: "bandit"
|
|
39194
39249
|
});
|
|
39195
39250
|
const [isProviderConfigOpen, setIsProviderConfigOpen] = (0, import_react53.useState)(false);
|
|
39196
39251
|
const [snackbarMessage, setSnackbarMessage] = (0, import_react53.useState)("");
|
|
@@ -39326,7 +39381,7 @@ var ProviderTab = () => {
|
|
|
39326
39381
|
setProviderConfig(applyDefaultModel({
|
|
39327
39382
|
...baseConfig,
|
|
39328
39383
|
gatewayUrl: packageSettings?.gatewayApiUrl || "",
|
|
39329
|
-
provider: "
|
|
39384
|
+
provider: "bandit"
|
|
39330
39385
|
}));
|
|
39331
39386
|
break;
|
|
39332
39387
|
case "playground" /* PLAYGROUND */:
|