drizzle-cube 0.4.18 → 0.4.20
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/adapters/express/index.cjs +6 -6
- package/dist/adapters/express/index.js +75 -74
- package/dist/adapters/fastify/index.cjs +5 -5
- package/dist/adapters/fastify/index.js +91 -90
- package/dist/adapters/{handler-DefTXJpi.js → handler-CbDMdSY5.js} +95 -82
- package/dist/adapters/{handler-hwoGzGex.cjs → handler-DtdjM1Vx.cjs} +18 -14
- package/dist/adapters/hono/index.cjs +6 -6
- package/dist/adapters/hono/index.js +66 -65
- package/dist/adapters/nextjs/index.cjs +5 -5
- package/dist/adapters/nextjs/index.js +116 -115
- package/dist/server/index.cjs +6 -2
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.js +14 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { h as H, Q as B, j, D as
|
|
2
|
-
import { handleDiscover as
|
|
1
|
+
import { h as H, Q as B, j, D as U } from "./mcp-transport-m1X1GtwG.js";
|
|
2
|
+
import { handleDiscover as Z, handleLoad as G } from "./utils.js";
|
|
3
3
|
function K(l) {
|
|
4
4
|
if (l.length === 0)
|
|
5
5
|
return "No cubes are currently available.";
|
|
@@ -172,7 +172,7 @@ function W(l) {
|
|
|
172
172
|
"",
|
|
173
173
|
"---",
|
|
174
174
|
"",
|
|
175
|
-
I(
|
|
175
|
+
I(U),
|
|
176
176
|
"",
|
|
177
177
|
"---",
|
|
178
178
|
"",
|
|
@@ -1752,11 +1752,11 @@ function Te(l, t, a) {
|
|
|
1752
1752
|
return t ?? {};
|
|
1753
1753
|
const d = { ...t }, u = a.measures ?? [], e = a.dimensions ?? [], r = (a.timeDimensions ?? []).map((s) => s.dimension);
|
|
1754
1754
|
for (const s of i.dropZones) {
|
|
1755
|
-
const
|
|
1756
|
-
if (Array.isArray(
|
|
1757
|
-
const
|
|
1755
|
+
const h = d[s.key];
|
|
1756
|
+
if (Array.isArray(h) ? h.length > 0 : !!h) continue;
|
|
1757
|
+
const c = s.acceptTypes ?? [];
|
|
1758
1758
|
if (s.key === "sizeField" || s.key === "colorField") {
|
|
1759
|
-
if (
|
|
1759
|
+
if (c.includes("measure")) {
|
|
1760
1760
|
const g = /* @__PURE__ */ new Set();
|
|
1761
1761
|
for (const C of i.dropZones) {
|
|
1762
1762
|
if (C.key === s.key) continue;
|
|
@@ -1769,7 +1769,7 @@ function Te(l, t, a) {
|
|
|
1769
1769
|
continue;
|
|
1770
1770
|
}
|
|
1771
1771
|
const n = [];
|
|
1772
|
-
if (
|
|
1772
|
+
if (c.includes("dimension") && n.push(...e), c.includes("timeDimension") && n.push(...r), c.includes("measure") && n.push(...u), n.length === 0) continue;
|
|
1773
1773
|
let x = n;
|
|
1774
1774
|
if (s.key === "series") {
|
|
1775
1775
|
const g = new Set(
|
|
@@ -1777,8 +1777,8 @@ function Te(l, t, a) {
|
|
|
1777
1777
|
);
|
|
1778
1778
|
if (x = n.filter((A) => !g.has(A)), x.length === 0) continue;
|
|
1779
1779
|
}
|
|
1780
|
-
const b = s.maxItems ?? 1 / 0,
|
|
1781
|
-
|
|
1780
|
+
const b = s.maxItems ?? 1 / 0, m = x.slice(0, b);
|
|
1781
|
+
m.length > 0 && (d[s.key] = m);
|
|
1782
1782
|
}
|
|
1783
1783
|
return d;
|
|
1784
1784
|
}
|
|
@@ -1788,7 +1788,7 @@ Chart config requirements by type:`];
|
|
|
1788
1788
|
for (const a of l) {
|
|
1789
1789
|
const i = P[a];
|
|
1790
1790
|
if (!i) continue;
|
|
1791
|
-
const d = i.description ?? "", u = i.useCase ?? "", e = [d, u].filter(Boolean).join(". "), o = e ? ` — ${e}.` : "", r = i.dropZones.filter((
|
|
1791
|
+
const d = i.description ?? "", u = i.useCase ?? "", e = [d, u].filter(Boolean).join(". "), o = e ? ` — ${e}.` : "", r = i.dropZones.filter((h) => h.mandatory);
|
|
1792
1792
|
if (r.length === 0 && !i.skipQuery) {
|
|
1793
1793
|
t.push(` ${a}${o} chartConfig auto-inferred from query.`);
|
|
1794
1794
|
continue;
|
|
@@ -1797,9 +1797,9 @@ Chart config requirements by type:`];
|
|
|
1797
1797
|
t.push(` ${a}${o} No query needed.`);
|
|
1798
1798
|
continue;
|
|
1799
1799
|
}
|
|
1800
|
-
const s = r.map((
|
|
1801
|
-
const
|
|
1802
|
-
return `${
|
|
1800
|
+
const s = r.map((h) => {
|
|
1801
|
+
const y = h.acceptTypes?.join("/") ?? "any", c = h.maxItems ? ` (max ${h.maxItems})` : "";
|
|
1802
|
+
return `${h.key}=[${y}]${c}`;
|
|
1803
1803
|
});
|
|
1804
1804
|
t.push(` ${a}${o} Requires ${s.join(", ")}.`);
|
|
1805
1805
|
}
|
|
@@ -2124,7 +2124,7 @@ The query is validated before adding. The portlet fetches its own data.`,
|
|
|
2124
2124
|
function Se(l) {
|
|
2125
2125
|
const { semanticLayer: t, securityContext: a } = l, i = /* @__PURE__ */ new Map();
|
|
2126
2126
|
i.set("discover_cubes", async (e) => {
|
|
2127
|
-
const o = await
|
|
2127
|
+
const o = await Z(t, {
|
|
2128
2128
|
topic: e.topic,
|
|
2129
2129
|
intent: e.intent,
|
|
2130
2130
|
limit: e.limit,
|
|
@@ -2143,51 +2143,57 @@ function Se(l) {
|
|
|
2143
2143
|
});
|
|
2144
2144
|
const u = (e, o) => {
|
|
2145
2145
|
const r = d.get(e), s = o === "measures" ? r?.measures : r?.dimensions;
|
|
2146
|
-
return !s || s.length === 0 ? "" : ` Available ${o}: ${s.slice(0, 5).map((
|
|
2146
|
+
return !s || s.length === 0 ? "" : ` Available ${o}: ${s.slice(0, 5).map((h) => `"${h}"`).join(", ")}`;
|
|
2147
2147
|
};
|
|
2148
2148
|
return i.set("execute_query", async (e) => {
|
|
2149
2149
|
try {
|
|
2150
|
-
const o = (
|
|
2151
|
-
if (!Array.isArray(
|
|
2150
|
+
const o = (y, c) => {
|
|
2151
|
+
if (!Array.isArray(y)) return;
|
|
2152
2152
|
const n = [], x = [];
|
|
2153
|
-
for (const b of
|
|
2153
|
+
for (const b of y) {
|
|
2154
2154
|
if (typeof b != "string") {
|
|
2155
2155
|
x.push(b);
|
|
2156
2156
|
continue;
|
|
2157
2157
|
}
|
|
2158
|
-
const
|
|
2159
|
-
if (
|
|
2160
|
-
n.push(`"${b}" is not valid — must be "CubeName.fieldName".${u(b,
|
|
2158
|
+
const m = b.split(".");
|
|
2159
|
+
if (m.length === 1) {
|
|
2160
|
+
n.push(`"${b}" is not valid — must be "CubeName.fieldName".${u(b, c)}`);
|
|
2161
2161
|
continue;
|
|
2162
2162
|
}
|
|
2163
|
-
if (
|
|
2164
|
-
const g = `${
|
|
2163
|
+
if (m.length === 3 && m[0] === m[1]) {
|
|
2164
|
+
const g = `${m[0]}.${m[2]}`;
|
|
2165
2165
|
x.push(g);
|
|
2166
2166
|
continue;
|
|
2167
2167
|
}
|
|
2168
|
-
if (
|
|
2169
|
-
n.push(`"${b}" is WRONG — "${
|
|
2168
|
+
if (m.length === 2 && m[0] === m[1]) {
|
|
2169
|
+
n.push(`"${b}" is WRONG — "${m[0]}" is the cube name, not a ${c.replace(/s$/, "")}.${u(m[0], c)}`);
|
|
2170
2170
|
continue;
|
|
2171
2171
|
}
|
|
2172
2172
|
x.push(b);
|
|
2173
2173
|
}
|
|
2174
2174
|
if (n.length > 0)
|
|
2175
|
-
throw new Error(`Invalid ${
|
|
2175
|
+
throw new Error(`Invalid ${c}:
|
|
2176
2176
|
${n.join(`
|
|
2177
2177
|
`)}`);
|
|
2178
2178
|
return x;
|
|
2179
2179
|
};
|
|
2180
2180
|
e.measures = o(e.measures, "measures") ?? e.measures, e.dimensions = o(e.dimensions, "dimensions") ?? e.dimensions;
|
|
2181
|
-
const r = (
|
|
2182
|
-
const
|
|
2183
|
-
return
|
|
2181
|
+
const r = (y) => {
|
|
2182
|
+
const c = y.split(".");
|
|
2183
|
+
return c.length === 3 && c[0] === c[1] ? `${c[0]}.${c[2]}` : y;
|
|
2184
2184
|
};
|
|
2185
2185
|
if (Array.isArray(e.filters))
|
|
2186
|
-
for (const
|
|
2187
|
-
typeof
|
|
2186
|
+
for (const y of e.filters)
|
|
2187
|
+
typeof y.member == "string" && (y.member = r(y.member));
|
|
2188
2188
|
if (Array.isArray(e.timeDimensions))
|
|
2189
|
-
for (const
|
|
2190
|
-
typeof
|
|
2189
|
+
for (const y of e.timeDimensions)
|
|
2190
|
+
typeof y.dimension == "string" && (y.dimension = r(y.dimension));
|
|
2191
|
+
if (e.order && typeof e.order == "object" && !Array.isArray(e.order)) {
|
|
2192
|
+
const y = {};
|
|
2193
|
+
for (const [c, n] of Object.entries(e.order))
|
|
2194
|
+
y[r(c)] = n;
|
|
2195
|
+
e.order = y;
|
|
2196
|
+
}
|
|
2191
2197
|
let s;
|
|
2192
2198
|
e.funnel ? s = { funnel: e.funnel } : e.flow ? s = { flow: e.flow } : e.retention ? s = { retention: e.retention } : s = {
|
|
2193
2199
|
measures: e.measures,
|
|
@@ -2197,12 +2203,12 @@ ${n.join(`
|
|
|
2197
2203
|
order: e.order,
|
|
2198
2204
|
limit: e.limit
|
|
2199
2205
|
};
|
|
2200
|
-
const
|
|
2206
|
+
const h = await G(t, a, { query: s });
|
|
2201
2207
|
return {
|
|
2202
2208
|
result: JSON.stringify({
|
|
2203
|
-
rowCount:
|
|
2204
|
-
data:
|
|
2205
|
-
annotation:
|
|
2209
|
+
rowCount: h.data.length,
|
|
2210
|
+
data: h.data,
|
|
2211
|
+
annotation: h.annotation
|
|
2206
2212
|
}, null, 2)
|
|
2207
2213
|
};
|
|
2208
2214
|
} catch (o) {
|
|
@@ -2239,38 +2245,38 @@ ${JSON.stringify(r, null, 2)}`,
|
|
|
2239
2245
|
isError: !0
|
|
2240
2246
|
};
|
|
2241
2247
|
}
|
|
2242
|
-
const
|
|
2243
|
-
if (!
|
|
2248
|
+
const h = t.validateQuery(s);
|
|
2249
|
+
if (!h.isValid)
|
|
2244
2250
|
return {
|
|
2245
2251
|
result: `Invalid query — fix these errors and retry:
|
|
2246
|
-
${
|
|
2252
|
+
${h.errors.join(`
|
|
2247
2253
|
`)}
|
|
2248
2254
|
|
|
2249
2255
|
Attempted query:
|
|
2250
2256
|
${JSON.stringify(s, null, 2)}`,
|
|
2251
2257
|
isError: !0
|
|
2252
2258
|
};
|
|
2253
|
-
const
|
|
2254
|
-
let
|
|
2255
|
-
if (
|
|
2256
|
-
|
|
2259
|
+
const y = !!(s.funnel || s.flow || s.retention);
|
|
2260
|
+
let c;
|
|
2261
|
+
if (y)
|
|
2262
|
+
c = e.chartConfig ?? {};
|
|
2257
2263
|
else {
|
|
2258
|
-
const b = Te(r, e.chartConfig, s),
|
|
2259
|
-
if (!
|
|
2264
|
+
const b = Te(r, e.chartConfig, s), m = Ce(r, b, s);
|
|
2265
|
+
if (!m.isValid)
|
|
2260
2266
|
return {
|
|
2261
2267
|
result: `Chart config invalid — fix these errors and retry:
|
|
2262
|
-
${
|
|
2268
|
+
${m.errors.join(`
|
|
2263
2269
|
`)}`,
|
|
2264
2270
|
isError: !0
|
|
2265
2271
|
};
|
|
2266
|
-
|
|
2272
|
+
c = b;
|
|
2267
2273
|
}
|
|
2268
2274
|
const n = `portlet-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, x = {
|
|
2269
2275
|
id: n,
|
|
2270
2276
|
title: e.title,
|
|
2271
2277
|
query: e.query,
|
|
2272
2278
|
chartType: r,
|
|
2273
|
-
chartConfig:
|
|
2279
|
+
chartConfig: c,
|
|
2274
2280
|
displayConfig: e.displayConfig
|
|
2275
2281
|
};
|
|
2276
2282
|
return {
|
|
@@ -2300,14 +2306,14 @@ ${c.errors.join(`
|
|
|
2300
2306
|
r.push(`Portlet "${n.title}": missing query`);
|
|
2301
2307
|
continue;
|
|
2302
2308
|
}
|
|
2303
|
-
let
|
|
2309
|
+
let m;
|
|
2304
2310
|
try {
|
|
2305
|
-
|
|
2311
|
+
m = JSON.parse(b);
|
|
2306
2312
|
} catch {
|
|
2307
2313
|
r.push(`Portlet "${n.title}": invalid JSON query`);
|
|
2308
2314
|
continue;
|
|
2309
2315
|
}
|
|
2310
|
-
const g = t.validateQuery(
|
|
2316
|
+
const g = t.validateQuery(m);
|
|
2311
2317
|
g.isValid || r.push(`Portlet "${n.title}": ${g.errors.join(", ")}`);
|
|
2312
2318
|
}
|
|
2313
2319
|
if (r.length > 0)
|
|
@@ -2319,7 +2325,7 @@ ${r.join(`
|
|
|
2319
2325
|
};
|
|
2320
2326
|
const s = {
|
|
2321
2327
|
portlets: o.map((n) => {
|
|
2322
|
-
const x = n.chartType, b = x === "markdown",
|
|
2328
|
+
const x = n.chartType, b = x === "markdown", m = b ? "query" : n.analysisType || "query", g = m === "funnel" ? "funnel" : m === "flow" ? "flow" : m === "retention" ? "retention" : "query", A = n.query || "{}";
|
|
2323
2329
|
let C;
|
|
2324
2330
|
try {
|
|
2325
2331
|
C = JSON.parse(A);
|
|
@@ -2352,13 +2358,13 @@ ${r.join(`
|
|
|
2352
2358
|
}),
|
|
2353
2359
|
filters: e.filters,
|
|
2354
2360
|
colorPalette: e.colorPalette
|
|
2355
|
-
},
|
|
2361
|
+
}, h = e.title, y = s.portlets.length, c = s.filters?.length || 0;
|
|
2356
2362
|
return {
|
|
2357
|
-
result: `Dashboard "${
|
|
2363
|
+
result: `Dashboard "${h}" created with ${y} portlets and ${c} filters.`,
|
|
2358
2364
|
sideEffect: {
|
|
2359
2365
|
type: "dashboard_saved",
|
|
2360
2366
|
data: {
|
|
2361
|
-
title:
|
|
2367
|
+
title: h,
|
|
2362
2368
|
description: e.description,
|
|
2363
2369
|
dashboardConfig: s
|
|
2364
2370
|
}
|
|
@@ -2373,14 +2379,14 @@ ${r.join(`
|
|
|
2373
2379
|
}), i;
|
|
2374
2380
|
}
|
|
2375
2381
|
async function* _e(l) {
|
|
2376
|
-
const { message: t, history: a, semanticLayer: i, securityContext: d, agentConfig: u, apiKey: e } = l, o = l.sessionId || crypto.randomUUID(), r = u.observability, s = crypto.randomUUID(),
|
|
2377
|
-
let
|
|
2382
|
+
const { message: t, history: a, semanticLayer: i, securityContext: d, agentConfig: u, apiKey: e } = l, o = l.sessionId || crypto.randomUUID(), r = u.observability, s = crypto.randomUUID(), h = Date.now();
|
|
2383
|
+
let y;
|
|
2378
2384
|
try {
|
|
2379
|
-
const
|
|
2385
|
+
const f = await import(
|
|
2380
2386
|
/* webpackIgnore: true */
|
|
2381
2387
|
"@anthropic-ai/sdk"
|
|
2382
2388
|
);
|
|
2383
|
-
|
|
2389
|
+
y = f.default || f.Anthropic || f;
|
|
2384
2390
|
} catch {
|
|
2385
2391
|
yield {
|
|
2386
2392
|
type: "error",
|
|
@@ -2390,7 +2396,14 @@ async function* _e(l) {
|
|
|
2390
2396
|
};
|
|
2391
2397
|
return;
|
|
2392
2398
|
}
|
|
2393
|
-
const
|
|
2399
|
+
const c = new y({ apiKey: e }), n = De(), x = Se({ semanticLayer: i, securityContext: d }), b = i.getMetadata();
|
|
2400
|
+
let m = W(b);
|
|
2401
|
+
l.systemContext && (m += `
|
|
2402
|
+
|
|
2403
|
+
## User Context
|
|
2404
|
+
|
|
2405
|
+
${l.systemContext}`);
|
|
2406
|
+
const g = u.model || "claude-sonnet-4-6", A = u.maxTurns || 25, C = u.maxTokens || 4096;
|
|
2394
2407
|
try {
|
|
2395
2408
|
r?.onChatStart?.({
|
|
2396
2409
|
traceId: s,
|
|
@@ -2403,13 +2416,13 @@ async function* _e(l) {
|
|
|
2403
2416
|
}
|
|
2404
2417
|
const k = [];
|
|
2405
2418
|
if (a && a.length > 0) {
|
|
2406
|
-
for (const
|
|
2407
|
-
if (
|
|
2408
|
-
k.push({ role: "user", content:
|
|
2409
|
-
else if (
|
|
2419
|
+
for (const f of a)
|
|
2420
|
+
if (f.role === "user")
|
|
2421
|
+
k.push({ role: "user", content: f.content });
|
|
2422
|
+
else if (f.role === "assistant") {
|
|
2410
2423
|
const F = [];
|
|
2411
|
-
if (
|
|
2412
|
-
for (const w of
|
|
2424
|
+
if (f.content && F.push({ type: "text", text: f.content }), f.toolCalls && f.toolCalls.length > 0) {
|
|
2425
|
+
for (const w of f.toolCalls)
|
|
2413
2426
|
F.push({
|
|
2414
2427
|
type: "tool_use",
|
|
2415
2428
|
id: w.id,
|
|
@@ -2418,25 +2431,25 @@ async function* _e(l) {
|
|
|
2418
2431
|
});
|
|
2419
2432
|
k.push({ role: "assistant", content: F }), k.push({
|
|
2420
2433
|
role: "user",
|
|
2421
|
-
content:
|
|
2434
|
+
content: f.toolCalls.map((w) => ({
|
|
2422
2435
|
type: "tool_result",
|
|
2423
2436
|
tool_use_id: w.id,
|
|
2424
2437
|
content: typeof w.result == "string" ? w.result : JSON.stringify(w.result ?? ""),
|
|
2425
2438
|
...w.status === "error" ? { is_error: !0 } : {}
|
|
2426
2439
|
}))
|
|
2427
2440
|
});
|
|
2428
|
-
} else F.length > 0 && k.push({ role: "assistant", content:
|
|
2441
|
+
} else F.length > 0 && k.push({ role: "assistant", content: f.content });
|
|
2429
2442
|
}
|
|
2430
2443
|
}
|
|
2431
2444
|
k.push({ role: "user", content: t });
|
|
2432
2445
|
let E = 0;
|
|
2433
2446
|
try {
|
|
2434
|
-
for (let
|
|
2435
|
-
E =
|
|
2436
|
-
const F = await
|
|
2447
|
+
for (let f = 0; f < A; f++) {
|
|
2448
|
+
E = f + 1;
|
|
2449
|
+
const F = await c.messages.create({
|
|
2437
2450
|
model: g,
|
|
2438
2451
|
max_tokens: C,
|
|
2439
|
-
system:
|
|
2452
|
+
system: m,
|
|
2440
2453
|
tools: n,
|
|
2441
2454
|
messages: k,
|
|
2442
2455
|
stream: !0
|
|
@@ -2488,7 +2501,7 @@ async function* _e(l) {
|
|
|
2488
2501
|
try {
|
|
2489
2502
|
r?.onGenerationEnd?.({
|
|
2490
2503
|
traceId: s,
|
|
2491
|
-
turn:
|
|
2504
|
+
turn: f,
|
|
2492
2505
|
model: g,
|
|
2493
2506
|
stopReason: O,
|
|
2494
2507
|
inputTokens: L,
|
|
@@ -2530,7 +2543,7 @@ async function* _e(l) {
|
|
|
2530
2543
|
try {
|
|
2531
2544
|
r?.onToolEnd?.({
|
|
2532
2545
|
traceId: s,
|
|
2533
|
-
turn:
|
|
2546
|
+
turn: f,
|
|
2534
2547
|
toolName: p,
|
|
2535
2548
|
toolUseId: S,
|
|
2536
2549
|
isError: !!T.isError,
|
|
@@ -2552,7 +2565,7 @@ async function* _e(l) {
|
|
|
2552
2565
|
try {
|
|
2553
2566
|
r?.onToolEnd?.({
|
|
2554
2567
|
traceId: s,
|
|
2555
|
-
turn:
|
|
2568
|
+
turn: f,
|
|
2556
2569
|
toolName: p,
|
|
2557
2570
|
toolUseId: S,
|
|
2558
2571
|
isError: !0,
|
|
@@ -2569,7 +2582,7 @@ async function* _e(l) {
|
|
|
2569
2582
|
traceId: s,
|
|
2570
2583
|
sessionId: o,
|
|
2571
2584
|
totalTurns: E,
|
|
2572
|
-
durationMs: Date.now() -
|
|
2585
|
+
durationMs: Date.now() - h
|
|
2573
2586
|
});
|
|
2574
2587
|
} catch {
|
|
2575
2588
|
}
|
|
@@ -2577,21 +2590,21 @@ async function* _e(l) {
|
|
|
2577
2590
|
type: "done",
|
|
2578
2591
|
data: { sessionId: o || "", traceId: s }
|
|
2579
2592
|
};
|
|
2580
|
-
} catch (
|
|
2593
|
+
} catch (f) {
|
|
2581
2594
|
try {
|
|
2582
2595
|
r?.onChatEnd?.({
|
|
2583
2596
|
traceId: s,
|
|
2584
2597
|
sessionId: o,
|
|
2585
2598
|
totalTurns: 0,
|
|
2586
|
-
durationMs: Date.now() -
|
|
2587
|
-
error:
|
|
2599
|
+
durationMs: Date.now() - h,
|
|
2600
|
+
error: f instanceof Error ? f.message : "Unknown error"
|
|
2588
2601
|
});
|
|
2589
2602
|
} catch {
|
|
2590
2603
|
}
|
|
2591
2604
|
yield {
|
|
2592
2605
|
type: "error",
|
|
2593
2606
|
data: {
|
|
2594
|
-
message: Fe(
|
|
2607
|
+
message: Fe(f)
|
|
2595
2608
|
}
|
|
2596
2609
|
};
|
|
2597
2610
|
}
|