@xbbg/langgraph 1.3.0 → 1.3.1

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 CHANGED
@@ -166,6 +166,7 @@ Extension helper tools:
166
166
  - `xbbg_ext_constants` - static constants and formatting helpers for dates, futures months, dividend types, and ETF/dividend columns.
167
167
  - `xbbg_ext_columns` - rename helpers for dividend, ETF, and earnings-shaped Bloomberg responses.
168
168
  - `xbbg_ext_calculate` - small numeric helper for level percentage calculations.
169
+ - `xbbg_ext_chart_spec` - renderer-neutral chart specs for frontend generative UI; converts bounded rows from `xbbg_bdh`, `xbbg_bdib`, holdings, depth, or already-shaped row data into Vega-Lite JSON.
169
170
 
170
171
  ```ts
171
172
  import { createBloombergExtTools, createAllBloombergTools } from "@xbbg/langgraph";
@@ -176,6 +177,34 @@ const allTools = createAllBloombergTools({
176
177
  });
177
178
  ```
178
179
 
180
+ ### Chart specs and generative UI frontends
181
+
182
+ `xbbg_ext_chart_spec` does not fetch Bloomberg data and does not render images. Use a data tool first, then pass its bounded rows into the chart-spec helper. The result is a portable visualization payload:
183
+
184
+ ```json
185
+ {
186
+ "kind": "xbbg.visualization",
187
+ "component": "xbbg_chart",
188
+ "renderer": "vega-lite",
189
+ "spec": { "data": { "values": [] }, "mark": { "type": "line" } }
190
+ }
191
+ ```
192
+
193
+ Frontend runtimes should register their own `xbbg_chart` component and render `data.spec` with their preferred Vega-Lite React wrapper. This keeps `@xbbg/langgraph` dependency-free and works with CopilotKit `useRenderTool`, assistant-ui data/generative UI renderers, Vercel AI SDK tool parts, or LangGraph `typedUi()` adapters.
194
+
195
+ Example tool input after a historical request:
196
+
197
+ ```json
198
+ {
199
+ "source": "bdh",
200
+ "rows": [{ "ticker": "AAPL US Equity", "date": "20240102", "value": 190.1 }],
201
+ "xField": "date",
202
+ "yFields": ["value"],
203
+ "seriesField": "ticker",
204
+ "title": "AAPL PX_LAST"
205
+ }
206
+ ```
207
+
179
208
  ## Engine handling
180
209
 
181
210
  By default the first tool invocation lazily imports `@xbbg/core`, calls `connect(engineConfig)`, and reuses the resulting engine across the tool set. Parallel LangGraph tool calls share the same in-flight initialization promise.
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ type XbbgCoreModule = typeof xbbg;
5
5
  type XbbgEngineLike = Pick<Awaited<ReturnType<XbbgCoreModule["connect"]>>, "bdp" | "bdh" | "bds" | "bdib" | "bdtick" | "bql" | "bsrch" | "bqr" | "bflds" | "beqs" | "yas" | "preferreds" | "corporateBonds" | "indexMembers" | "resolveIsins" | "issuerIsins" | "etfHoldings" | "stream" | "mktbar" | "depth">;
6
6
  type XbbgCoreLike = Pick<XbbgCoreModule, "connect" | "ext">;
7
7
 
8
- declare const BLOOMBERG_TOOL_NAMES: readonly ["xbbg_bdp", "xbbg_bdh", "xbbg_bds", "xbbg_bdib", "xbbg_bdtick", "xbbg_bql", "xbbg_bsrch", "xbbg_bqr", "xbbg_bflds", "xbbg_beqs", "xbbg_yas", "xbbg_preferreds", "xbbg_corporate_bonds", "xbbg_index_members", "xbbg_resolve_isins", "xbbg_issuer_isins", "xbbg_etf_holdings", "xbbg_stream_snapshot", "xbbg_mktbar_snapshot", "xbbg_depth_snapshot", "xbbg_ext_ticker", "xbbg_ext_futures", "xbbg_ext_cdx", "xbbg_ext_currency", "xbbg_ext_bql_builder", "xbbg_ext_market_session", "xbbg_ext_yas_overrides", "xbbg_ext_constants", "xbbg_ext_columns", "xbbg_ext_calculate"];
8
+ declare const BLOOMBERG_TOOL_NAMES: readonly ["xbbg_bdp", "xbbg_bdh", "xbbg_bds", "xbbg_bdib", "xbbg_bdtick", "xbbg_bql", "xbbg_bsrch", "xbbg_bqr", "xbbg_bflds", "xbbg_beqs", "xbbg_yas", "xbbg_preferreds", "xbbg_corporate_bonds", "xbbg_index_members", "xbbg_resolve_isins", "xbbg_issuer_isins", "xbbg_etf_holdings", "xbbg_stream_snapshot", "xbbg_mktbar_snapshot", "xbbg_depth_snapshot", "xbbg_ext_ticker", "xbbg_ext_futures", "xbbg_ext_cdx", "xbbg_ext_currency", "xbbg_ext_bql_builder", "xbbg_ext_chart_spec", "xbbg_ext_market_session", "xbbg_ext_yas_overrides", "xbbg_ext_constants", "xbbg_ext_columns", "xbbg_ext_calculate"];
9
9
  type BloombergToolName = (typeof BLOOMBERG_TOOL_NAMES)[number];
10
10
  interface BloombergToolsOptions {
11
11
  readonly engine?: XbbgEngineLike;
@@ -75,7 +75,7 @@ interface BloombergToolInstructionsOptions {
75
75
  }
76
76
  declare function getBloombergToolInstructions(options?: BloombergToolInstructionsOptions): string;
77
77
 
78
- declare const BLOOMBERG_EXT_TOOL_NAMES: readonly ("xbbg_bdp" | "xbbg_bdh" | "xbbg_bds" | "xbbg_bdib" | "xbbg_bdtick" | "xbbg_bql" | "xbbg_bsrch" | "xbbg_bqr" | "xbbg_bflds" | "xbbg_beqs" | "xbbg_yas" | "xbbg_preferreds" | "xbbg_corporate_bonds" | "xbbg_index_members" | "xbbg_resolve_isins" | "xbbg_issuer_isins" | "xbbg_etf_holdings" | "xbbg_stream_snapshot" | "xbbg_mktbar_snapshot" | "xbbg_depth_snapshot" | "xbbg_ext_ticker" | "xbbg_ext_futures" | "xbbg_ext_cdx" | "xbbg_ext_currency" | "xbbg_ext_bql_builder" | "xbbg_ext_market_session" | "xbbg_ext_yas_overrides" | "xbbg_ext_constants" | "xbbg_ext_columns" | "xbbg_ext_calculate")[];
78
+ declare const BLOOMBERG_EXT_TOOL_NAMES: readonly ("xbbg_bdp" | "xbbg_bdh" | "xbbg_bds" | "xbbg_bdib" | "xbbg_bdtick" | "xbbg_bql" | "xbbg_bsrch" | "xbbg_bqr" | "xbbg_bflds" | "xbbg_beqs" | "xbbg_yas" | "xbbg_preferreds" | "xbbg_corporate_bonds" | "xbbg_index_members" | "xbbg_resolve_isins" | "xbbg_issuer_isins" | "xbbg_etf_holdings" | "xbbg_stream_snapshot" | "xbbg_mktbar_snapshot" | "xbbg_depth_snapshot" | "xbbg_ext_ticker" | "xbbg_ext_futures" | "xbbg_ext_cdx" | "xbbg_ext_currency" | "xbbg_ext_bql_builder" | "xbbg_ext_chart_spec" | "xbbg_ext_market_session" | "xbbg_ext_yas_overrides" | "xbbg_ext_constants" | "xbbg_ext_columns" | "xbbg_ext_calculate")[];
79
79
  declare function createExtTickerTool(options?: BloombergToolsOptions): BloombergTool;
80
80
  declare function createExtFuturesTool(options?: BloombergToolsOptions): BloombergTool;
81
81
  declare function createExtCdxTool(options?: BloombergToolsOptions): BloombergTool;
@@ -86,6 +86,7 @@ declare function createExtYasOverridesTool(options?: BloombergToolsOptions): Blo
86
86
  declare function createExtConstantsTool(options?: BloombergToolsOptions): BloombergTool;
87
87
  declare function createExtColumnsTool(options?: BloombergToolsOptions): BloombergTool;
88
88
  declare function createExtCalculateTool(options?: BloombergToolsOptions): BloombergTool;
89
+ declare function createExtChartSpecTool(options?: BloombergToolsOptions): BloombergTool;
89
90
  declare function createBloombergExtTools(options?: BloombergToolsOptions): BloombergTool[];
90
91
 
91
92
  interface ToolEnvelope {
@@ -111,6 +112,68 @@ interface ToolInvocationConfig {
111
112
  */
112
113
  declare function toolParameterJsonSchema(toolInstance: StructuredToolInterface): Record<string, unknown>;
113
114
 
115
+ type BloombergChartSource = "bdh" | "bdib" | "holdings" | "depth" | "rows";
116
+ type ChartKind = "line" | "area" | "bar" | "scatter" | "candlestick" | "depth";
117
+ type ChartRenderer = "vega-lite";
118
+ type ChartScalar = string | number | boolean | null;
119
+ type ChartRow = Readonly<Record<string, ChartScalar>>;
120
+ interface ChartSpecInput {
121
+ readonly source: BloombergChartSource;
122
+ readonly rows: readonly ChartRow[];
123
+ readonly renderer?: ChartRenderer;
124
+ readonly chart?: ChartKind;
125
+ readonly title?: string;
126
+ readonly xField?: string;
127
+ readonly yFields?: readonly string[];
128
+ readonly seriesField?: string;
129
+ readonly labelField?: string;
130
+ readonly valueField?: string;
131
+ readonly openField?: string;
132
+ readonly highField?: string;
133
+ readonly lowField?: string;
134
+ readonly closeField?: string;
135
+ readonly sideField?: string;
136
+ readonly priceField?: string;
137
+ readonly sizeField?: string;
138
+ readonly maxPoints?: number;
139
+ }
140
+
141
+ interface ChartSpecSummary {
142
+ readonly chart: ChartKind;
143
+ readonly inputRows: number;
144
+ readonly renderer: "vega-lite";
145
+ readonly rowCount: number;
146
+ readonly source: BloombergChartSource;
147
+ readonly title: string;
148
+ readonly truncatedInput: boolean;
149
+ readonly xField?: string;
150
+ readonly yFields: readonly string[];
151
+ readonly seriesField?: string;
152
+ }
153
+ interface VegaLiteSpec {
154
+ readonly $schema: "https://vega.github.io/schema/vega-lite/v5.json";
155
+ readonly data: {
156
+ readonly values: readonly ChartRow[];
157
+ };
158
+ readonly description: string;
159
+ readonly title: string;
160
+ readonly [key: string]: unknown;
161
+ }
162
+ interface ChartSpecOutput {
163
+ readonly kind: "xbbg.visualization";
164
+ readonly version: 1;
165
+ readonly component: "xbbg_chart";
166
+ readonly renderer: "vega-lite";
167
+ readonly rowCount: number;
168
+ readonly inputRowCount: number;
169
+ readonly truncatedInput: boolean;
170
+ readonly source: BloombergChartSource;
171
+ readonly chart: ChartKind;
172
+ readonly summary: ChartSpecSummary;
173
+ readonly spec: VegaLiteSpec;
174
+ readonly warnings: readonly string[];
175
+ }
176
+
114
177
  declare function createAllBloombergTools(options?: BloombergToolsOptions): BloombergTool[];
115
178
 
116
- export { BLOOMBERG_EXT_TOOL_NAMES, BLOOMBERG_TOOL_INSTRUCTIONS, BLOOMBERG_TOOL_NAMES, type BloombergTool, type BloombergToolInstructionsOptions, type BloombergToolName, type BloombergToolsOptions, DEFAULT_ENGINE_REQUEST_TIMEOUT_MS, type NormalizedBloombergToolsOptions, type ToolEnvelope, type ToolInvocationConfig, createAllBloombergTools, createBdhTool, createBdibTool, createBdpTool, createBdsTool, createBdtickTool, createBeqsTool, createBfldsTool, createBloombergExtTools, createBloombergTools, createBqlTool, createBqrTool, createBsrchTool, createCorporateBondsTool, createDepthSnapshotTool, createEtfHoldingsTool, createExtBqlBuilderTool, createExtCalculateTool, createExtCdxTool, createExtColumnsTool, createExtConstantsTool, createExtCurrencyTool, createExtFuturesTool, createExtMarketSessionTool, createExtTickerTool, createExtYasOverridesTool, createIndexMembersTool, createIssuerIsinsTool, createMktbarSnapshotTool, createPreferredsTool, createResolveIsinsTool, createStreamSnapshotTool, createYasTool, getBloombergToolInstructions, toolParameterJsonSchema };
179
+ export { BLOOMBERG_EXT_TOOL_NAMES, BLOOMBERG_TOOL_INSTRUCTIONS, BLOOMBERG_TOOL_NAMES, type BloombergChartSource, type BloombergTool, type BloombergToolInstructionsOptions, type BloombergToolName, type BloombergToolsOptions, type ChartKind, type ChartRenderer, type ChartRow, type ChartScalar, type ChartSpecInput, type ChartSpecOutput, type ChartSpecSummary, DEFAULT_ENGINE_REQUEST_TIMEOUT_MS, type NormalizedBloombergToolsOptions, type ToolEnvelope, type ToolInvocationConfig, type VegaLiteSpec, createAllBloombergTools, createBdhTool, createBdibTool, createBdpTool, createBdsTool, createBdtickTool, createBeqsTool, createBfldsTool, createBloombergExtTools, createBloombergTools, createBqlTool, createBqrTool, createBsrchTool, createCorporateBondsTool, createDepthSnapshotTool, createEtfHoldingsTool, createExtBqlBuilderTool, createExtCalculateTool, createExtCdxTool, createExtChartSpecTool, createExtColumnsTool, createExtConstantsTool, createExtCurrencyTool, createExtFuturesTool, createExtMarketSessionTool, createExtTickerTool, createExtYasOverridesTool, createIndexMembersTool, createIssuerIsinsTool, createMktbarSnapshotTool, createPreferredsTool, createResolveIsinsTool, createStreamSnapshotTool, createYasTool, getBloombergToolInstructions, toolParameterJsonSchema };
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ var BLOOMBERG_TOOL_NAMES = [
51
51
  "xbbg_ext_cdx",
52
52
  "xbbg_ext_currency",
53
53
  "xbbg_ext_bql_builder",
54
+ "xbbg_ext_chart_spec",
54
55
  "xbbg_ext_market_session",
55
56
  "xbbg_ext_yas_overrides",
56
57
  "xbbg_ext_constants",
@@ -244,12 +245,12 @@ function rowCountOf(value) {
244
245
  if (typeof value !== "object" || value === null) {
245
246
  return null;
246
247
  }
247
- const record2 = value;
248
- const rowCount = record2.rowCount;
248
+ const record3 = value;
249
+ const rowCount = record3.rowCount;
249
250
  if (typeof rowCount === "number" && Number.isInteger(rowCount) && rowCount >= 0) {
250
251
  return rowCount;
251
252
  }
252
- const updateCount = record2.updateCount;
253
+ const updateCount = record3.updateCount;
253
254
  if (typeof updateCount === "number" && Number.isInteger(updateCount) && updateCount >= 0) {
254
255
  return updateCount;
255
256
  }
@@ -401,6 +402,389 @@ function createBloombergStructuredTool(func, fields) {
401
402
  );
402
403
  }
403
404
 
405
+ // src/chart-spec.ts
406
+ var VEGA_SCHEMA = "https://vega.github.io/schema/vega-lite/v5.json";
407
+ var COMPONENT_NAME = "xbbg_chart";
408
+ var X_FIELD_CANDIDATES = ["date", "time", "datetime", "timestamp"];
409
+ var LABEL_FIELD_CANDIDATES = ["ticker", "security", "member", "name", "label"];
410
+ var SERIES_FIELD_CANDIDATES = ["ticker", "security", "field", "side", "category"];
411
+ var VALUE_FIELD_CANDIDATES = [
412
+ "value",
413
+ "PX_LAST",
414
+ "close",
415
+ "price",
416
+ "weight",
417
+ "marketValue",
418
+ "market_value"
419
+ ];
420
+ var OPEN_FIELD_CANDIDATES = ["open", "OPEN", "PX_OPEN"];
421
+ var HIGH_FIELD_CANDIDATES = ["high", "HIGH", "PX_HIGH"];
422
+ var LOW_FIELD_CANDIDATES = ["low", "LOW", "PX_LOW"];
423
+ var CLOSE_FIELD_CANDIDATES = ["close", "CLOSE", "PX_LAST", "last", "value"];
424
+ var SIDE_FIELD_CANDIDATES = ["side", "SIDE", "type"];
425
+ var PRICE_FIELD_CANDIDATES = ["price", "PRICE", "px", "PX"];
426
+ var SIZE_FIELD_CANDIDATES = ["size", "SIZE", "quantity", "qty", "volume"];
427
+ function defaultChartForSource(source) {
428
+ switch (source) {
429
+ case "bdib":
430
+ return "candlestick";
431
+ case "depth":
432
+ return "depth";
433
+ case "holdings":
434
+ return "bar";
435
+ case "bdh":
436
+ case "rows":
437
+ return "line";
438
+ }
439
+ }
440
+ function fieldExists(rows, field) {
441
+ for (const row of rows) {
442
+ if (Object.prototype.hasOwnProperty.call(row, field)) {
443
+ return true;
444
+ }
445
+ }
446
+ return false;
447
+ }
448
+ function findCandidateField(rows, candidates) {
449
+ for (const candidate of candidates) {
450
+ if (fieldExists(rows, candidate)) {
451
+ return candidate;
452
+ }
453
+ }
454
+ const first = rows[0];
455
+ if (first === void 0) {
456
+ return void 0;
457
+ }
458
+ const keys = Object.keys(first);
459
+ for (const candidate of candidates) {
460
+ const lower = candidate.toLowerCase();
461
+ const match = keys.find((key) => key.toLowerCase() === lower);
462
+ if (match !== void 0 && fieldExists(rows, match)) {
463
+ return match;
464
+ }
465
+ }
466
+ return void 0;
467
+ }
468
+ function requireField(rows, field, label, candidates) {
469
+ const resolved = field ?? findCandidateField(rows, candidates);
470
+ if (resolved === void 0 || !fieldExists(rows, resolved)) {
471
+ throw new Error(
472
+ `Missing ${label}; pass ${label} explicitly or include one of: ${candidates.join(", ")}`
473
+ );
474
+ }
475
+ return resolved;
476
+ }
477
+ function hasFiniteNumber(rows, field) {
478
+ for (const row of rows) {
479
+ if (typeof row[field] === "number" && Number.isFinite(row[field])) {
480
+ return true;
481
+ }
482
+ }
483
+ return false;
484
+ }
485
+ function firstNumericField(rows, excludedField) {
486
+ const first = rows[0];
487
+ if (first === void 0) {
488
+ return void 0;
489
+ }
490
+ for (const key of Object.keys(first)) {
491
+ if (key !== excludedField && hasFiniteNumber(rows, key)) {
492
+ return key;
493
+ }
494
+ }
495
+ return void 0;
496
+ }
497
+ function requireNumericField(rows, field, label) {
498
+ if (!hasFiniteNumber(rows, field)) {
499
+ throw new Error(`${label} (${field}) must contain at least one finite numeric value`);
500
+ }
501
+ }
502
+ function inferVegaType(rows, field) {
503
+ for (const row of rows) {
504
+ const value = row[field];
505
+ if (typeof value === "number") {
506
+ return "quantitative";
507
+ }
508
+ if (typeof value === "string" && (/^\d{4}-\d{2}-\d{2}(?:$|[T\s])/u.test(value) || /^\d{8}$/u.test(value))) {
509
+ return "temporal";
510
+ }
511
+ }
512
+ return "nominal";
513
+ }
514
+ function normalizeTemporalRows(rows, field) {
515
+ let normalized;
516
+ for (let index = 0; index < rows.length; index += 1) {
517
+ const row = rows[index];
518
+ if (row === void 0) {
519
+ continue;
520
+ }
521
+ const value = row[field];
522
+ if (typeof value !== "string" || !/^\d{8}$/u.test(value)) {
523
+ normalized?.push(row);
524
+ continue;
525
+ }
526
+ normalized ??= rows.slice(0, index);
527
+ normalized.push({
528
+ ...row,
529
+ [field]: `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`
530
+ });
531
+ }
532
+ return normalized ?? rows;
533
+ }
534
+ function tooltip(fields) {
535
+ return fields.map((field) => ({
536
+ field,
537
+ type: field === "_xbbg_value" ? "quantitative" : "nominal"
538
+ }));
539
+ }
540
+ function datumField(field) {
541
+ return `datum[${JSON.stringify(field)}]`;
542
+ }
543
+ function buildGenericSpec(input, rows, chart, title) {
544
+ const xField = requireField(rows, input.xField, "xField", X_FIELD_CANDIDATES);
545
+ let yFields;
546
+ if (input.yFields !== void 0) {
547
+ yFields = input.yFields;
548
+ } else {
549
+ const yField = findCandidateField(rows, VALUE_FIELD_CANDIDATES) ?? firstNumericField(rows, xField);
550
+ if (yField === void 0) {
551
+ throw new Error("Missing yFields; include at least one numeric value field");
552
+ }
553
+ yFields = [yField];
554
+ }
555
+ if (yFields.length === 0) {
556
+ throw new Error("Missing yFields; include at least one numeric value field");
557
+ }
558
+ for (const field of yFields) {
559
+ if (!fieldExists(rows, field)) {
560
+ throw new Error(`Missing y field: ${field}`);
561
+ }
562
+ requireNumericField(rows, field, "yField");
563
+ }
564
+ const normalizedRows = inferVegaType(rows, xField) === "temporal" ? normalizeTemporalRows(rows, xField) : rows;
565
+ const seriesField = input.seriesField ?? (yFields.length === 1 ? findCandidateField(rows, SERIES_FIELD_CANDIDATES) : void 0);
566
+ if (seriesField !== void 0 && !fieldExists(rows, seriesField)) {
567
+ throw new Error(`Missing series field: ${seriesField}`);
568
+ }
569
+ const mark = chart === "scatter" ? "point" : chart;
570
+ const encoding = {
571
+ x: { field: xField, title: xField, type: inferVegaType(normalizedRows, xField) }
572
+ };
573
+ const transform = [];
574
+ if (yFields.length === 1) {
575
+ const yField = yFields[0];
576
+ if (yField === void 0) {
577
+ throw new Error("Missing yFields; include at least one numeric value field");
578
+ }
579
+ encoding.y = { field: yField, title: yField, type: "quantitative" };
580
+ if (seriesField !== void 0) {
581
+ encoding.color = { field: seriesField, title: seriesField, type: "nominal" };
582
+ }
583
+ encoding.tooltip = tooltip([
584
+ xField,
585
+ ...seriesField === void 0 ? [] : [seriesField],
586
+ yField
587
+ ]);
588
+ } else {
589
+ transform.push({ as: ["_xbbg_series", "_xbbg_value"], fold: yFields });
590
+ encoding.y = { field: "_xbbg_value", title: "value", type: "quantitative" };
591
+ encoding.color = { field: "_xbbg_series", title: "series", type: "nominal" };
592
+ if (seriesField !== void 0) {
593
+ encoding.detail = { field: seriesField, type: "nominal" };
594
+ }
595
+ encoding.tooltip = tooltip([
596
+ xField,
597
+ ...seriesField === void 0 ? [] : [seriesField],
598
+ "_xbbg_series",
599
+ "_xbbg_value"
600
+ ]);
601
+ }
602
+ const spec = {
603
+ $schema: VEGA_SCHEMA,
604
+ data: { values: normalizedRows },
605
+ description: `xbbg ${chart} chart spec for ${input.source}`,
606
+ mark: { type: mark, tooltip: true },
607
+ title,
608
+ ...transform.length === 0 ? {} : { transform },
609
+ encoding
610
+ };
611
+ return { spec, xField, yFields, ...seriesField === void 0 ? {} : { seriesField } };
612
+ }
613
+ function buildBarSpec(input, rows, title) {
614
+ const xField = requireField(
615
+ rows,
616
+ input.xField ?? input.labelField,
617
+ "labelField",
618
+ LABEL_FIELD_CANDIDATES
619
+ );
620
+ const yField = requireField(
621
+ rows,
622
+ input.valueField ?? input.yFields?.[0],
623
+ "valueField",
624
+ VALUE_FIELD_CANDIDATES
625
+ );
626
+ requireNumericField(rows, yField, "valueField");
627
+ const seriesField = input.seriesField;
628
+ if (seriesField !== void 0 && !fieldExists(rows, seriesField)) {
629
+ throw new Error(`Missing series field: ${seriesField}`);
630
+ }
631
+ const encoding = {
632
+ x: { field: xField, sort: "-y", title: xField, type: inferVegaType(rows, xField) },
633
+ y: { field: yField, title: yField, type: "quantitative" },
634
+ tooltip: tooltip([xField, ...seriesField === void 0 ? [] : [seriesField], yField])
635
+ };
636
+ if (seriesField !== void 0) {
637
+ encoding.color = { field: seriesField, title: seriesField, type: "nominal" };
638
+ }
639
+ return {
640
+ spec: {
641
+ $schema: VEGA_SCHEMA,
642
+ data: { values: rows },
643
+ description: `xbbg bar chart spec for ${input.source}`,
644
+ encoding,
645
+ mark: { type: "bar", tooltip: true },
646
+ title
647
+ },
648
+ xField,
649
+ yFields: [yField],
650
+ ...seriesField === void 0 ? {} : { seriesField }
651
+ };
652
+ }
653
+ function buildCandlestickSpec(input, rows, title) {
654
+ const xField = requireField(rows, input.xField, "xField", X_FIELD_CANDIDATES);
655
+ const openField = requireField(rows, input.openField, "openField", OPEN_FIELD_CANDIDATES);
656
+ const highField = requireField(rows, input.highField, "highField", HIGH_FIELD_CANDIDATES);
657
+ const lowField = requireField(rows, input.lowField, "lowField", LOW_FIELD_CANDIDATES);
658
+ const closeField = requireField(rows, input.closeField, "closeField", CLOSE_FIELD_CANDIDATES);
659
+ for (const [label, field] of [
660
+ ["openField", openField],
661
+ ["highField", highField],
662
+ ["lowField", lowField],
663
+ ["closeField", closeField]
664
+ ]) {
665
+ requireNumericField(rows, field, label);
666
+ }
667
+ const normalizedRows = inferVegaType(rows, xField) === "temporal" ? normalizeTemporalRows(rows, xField) : rows;
668
+ const color = {
669
+ condition: { test: `${datumField(closeField)} >= ${datumField(openField)}`, value: "#137333" },
670
+ value: "#c5221f"
671
+ };
672
+ return {
673
+ spec: {
674
+ $schema: VEGA_SCHEMA,
675
+ data: { values: normalizedRows },
676
+ description: `xbbg candlestick chart spec for ${input.source}`,
677
+ encoding: {
678
+ x: { field: xField, title: xField, type: inferVegaType(normalizedRows, xField) }
679
+ },
680
+ layer: [
681
+ {
682
+ mark: "rule",
683
+ encoding: {
684
+ color,
685
+ tooltip: tooltip([xField, openField, highField, lowField, closeField]),
686
+ y: { field: lowField, title: "price", type: "quantitative" },
687
+ y2: { field: highField }
688
+ }
689
+ },
690
+ {
691
+ mark: "bar",
692
+ encoding: {
693
+ color,
694
+ y: { field: openField, title: "price", type: "quantitative" },
695
+ y2: { field: closeField }
696
+ }
697
+ }
698
+ ],
699
+ title
700
+ },
701
+ xField,
702
+ yFields: [openField, highField, lowField, closeField]
703
+ };
704
+ }
705
+ function buildDepthSpec(input, rows, title) {
706
+ const priceField = requireField(
707
+ rows,
708
+ input.priceField ?? input.xField,
709
+ "priceField",
710
+ PRICE_FIELD_CANDIDATES
711
+ );
712
+ const sizeField = requireField(
713
+ rows,
714
+ input.sizeField ?? input.valueField ?? input.yFields?.[0],
715
+ "sizeField",
716
+ SIZE_FIELD_CANDIDATES
717
+ );
718
+ const sideField = requireField(
719
+ rows,
720
+ input.sideField ?? input.seriesField,
721
+ "sideField",
722
+ SIDE_FIELD_CANDIDATES
723
+ );
724
+ requireNumericField(rows, priceField, "priceField");
725
+ requireNumericField(rows, sizeField, "sizeField");
726
+ return {
727
+ spec: {
728
+ $schema: VEGA_SCHEMA,
729
+ data: { values: rows },
730
+ description: `xbbg market depth chart spec for ${input.source}`,
731
+ encoding: {
732
+ color: { field: sideField, title: sideField, type: "nominal" },
733
+ tooltip: tooltip([sideField, priceField, sizeField]),
734
+ x: { field: priceField, title: priceField, type: "quantitative" },
735
+ y: { field: sizeField, title: sizeField, type: "quantitative" }
736
+ },
737
+ mark: { type: "bar", tooltip: true },
738
+ title
739
+ },
740
+ xField: priceField,
741
+ yFields: [sizeField],
742
+ seriesField: sideField
743
+ };
744
+ }
745
+ function createChartSpec(input) {
746
+ const maxPoints = input.maxPoints ?? input.rows.length;
747
+ const rows = input.rows.length > maxPoints ? input.rows.slice(0, maxPoints) : input.rows;
748
+ if (rows.length === 0) {
749
+ throw new Error("rows must contain at least one chart data row");
750
+ }
751
+ const chart = input.chart ?? defaultChartForSource(input.source);
752
+ const title = input.title ?? `${input.source} ${chart}`;
753
+ const warnings = [];
754
+ if (rows.length !== input.rows.length) {
755
+ warnings.push(
756
+ `Chart spec contains first ${rows.length} of ${input.rows.length} rows; narrow the upstream request for a complete visualization.`
757
+ );
758
+ }
759
+ const built = chart === "candlestick" ? buildCandlestickSpec(input, rows, title) : chart === "depth" ? buildDepthSpec(input, rows, title) : chart === "bar" ? buildBarSpec(input, rows, title) : buildGenericSpec(input, rows, chart, title);
760
+ const summary = {
761
+ chart,
762
+ inputRows: input.rows.length,
763
+ renderer: "vega-lite",
764
+ rowCount: rows.length,
765
+ source: input.source,
766
+ title,
767
+ truncatedInput: rows.length !== input.rows.length,
768
+ xField: built.xField,
769
+ yFields: built.yFields,
770
+ ...built.seriesField === void 0 ? {} : { seriesField: built.seriesField }
771
+ };
772
+ return {
773
+ kind: "xbbg.visualization",
774
+ version: 1,
775
+ component: COMPONENT_NAME,
776
+ renderer: "vega-lite",
777
+ rowCount: rows.length,
778
+ inputRowCount: input.rows.length,
779
+ truncatedInput: rows.length !== input.rows.length,
780
+ source: input.source,
781
+ chart,
782
+ summary,
783
+ spec: built.spec,
784
+ warnings
785
+ };
786
+ }
787
+
404
788
  // src/cdx-fields.ts
405
789
  var CDX_INFO_FIELDS = Object.freeze([
406
790
  "ROLLING_SERIES",
@@ -492,6 +876,7 @@ var OPTIONAL_EXTENSION_INSTRUCTIONS = [
492
876
  "- xbbg_ext_cdx: CDX ticker workflow support. Use parse_cdx_ticker to understand a CDX ticker, previous_cdx_series to roll back a series, cdx_gen_to_specific to resolve a generic CDX to a target series, and cdx_info/cdx_pricing/cdx_risk for predefined BDP field bundles. cdx_pricing and cdx_risk accept recoveryRate, which becomes the CDS_RR override.",
493
877
  "- xbbg_ext_currency: currency-planning helpers. build_fx_pair constructs the Bloomberg FX pair and conversion factor, same_currency avoids unnecessary conversion, and currencies_needing_conversion identifies which currencies differ from a target before requesting converted values.",
494
878
  "- xbbg_ext_bql_builder: safe BQL generators for common xbbg workflows. Use build_preferreds_query for preferred-stock discovery from an equity, build_corporate_bonds_query for company bond universes with optional currency/active filters, and build_etf_holdings_query for ETF constituents. Prefer these builders over hand-writing those BQL shapes.",
879
+ "- xbbg_ext_chart_spec: renderer-neutral chart spec helper. Convert bounded rows from xbbg_bdh, xbbg_bdib, holdings, depth, or already-shaped row data into a Vega-Lite JSON spec for frontend rendering; do not use it as proof that Bloomberg data was fetched.",
495
880
  "- xbbg_ext_market_session: exchange calendar/timezone support. derive_sessions turns day session times into session blocks, infer_timezone maps country codes to timezones, session_times_to_utc converts local sessions to UTC, get_market_rule gets MIC/exchange rules, default_turnover_dates and default_bqr_datetimes provide bounded defaults, and get/list_exchange_override inspect configured exchange metadata.",
496
881
  "- xbbg_ext_yas_overrides: builds flat YAS override maps for fixed-income BDP requests when the lower-level BDP workflow is required. Prefer xbbg_yas for actual YAS recipe fields.",
497
882
  "- xbbg_ext_constants: static lookup/format helpers for date parsing/formatting, futures month code/name mappings, dividend type mappings, and known dividend/ETF output columns.",
@@ -547,6 +932,7 @@ var EXT_FUTURES_DESCRIPTION = "Futures helpers for contract construction and sel
547
932
  var EXT_CDX_DESCRIPTION = "CDX helpers for parsing, series rolling/resolution, and predefined info/pricing/risk BDP field bundles.";
548
933
  var EXT_CURRENCY_DESCRIPTION = "Currency planning helpers: build FX pairs, test same-currency requests, and find currencies needing conversion.";
549
934
  var EXT_BQL_BUILDER_DESCRIPTION = "BQL builders for preferred stocks, corporate bonds, and ETF holdings. Prefer to construct those bounded BQL shapes before xbbg_bql.";
935
+ var EXT_CHART_SPEC_DESCRIPTION = "Renderer-neutral chart spec helper for frontend generative UI. Converts bounded Bloomberg rows from bdh, bdib, holdings, depth, or already-shaped row data into a Vega-Lite JSON spec; it does not fetch Bloomberg data or render images.";
550
936
  var EXT_MARKET_SESSION_DESCRIPTION = "Market session and timezone helpers for deriving sessions, UTC windows, market rules, exchange metadata, turnover defaults, and BQR datetime defaults.";
551
937
  var EXT_YAS_OVERRIDES_DESCRIPTION = "Build flat Bloomberg YAS override maps for fixed-income analytics fields.";
552
938
  var EXT_CONSTANTS_DESCRIPTION = "Static Bloomberg helper constants for date parsing/formatting, futures months, dividend types, and ETF/dividend columns.";
@@ -802,6 +1188,35 @@ function calculateSchema(options) {
802
1188
  }
803
1189
  });
804
1190
  }
1191
+ function chartSpecSchema(options) {
1192
+ const chartScalar = z__namespace.union([
1193
+ z__namespace.string().trim().max(options.maxStringChars),
1194
+ z__namespace.number(),
1195
+ z__namespace.boolean(),
1196
+ z__namespace.null()
1197
+ ]);
1198
+ const fieldName = nonEmptyString(options, "Input row field name.");
1199
+ return z__namespace.object({
1200
+ chart: z__namespace.enum(["line", "area", "bar", "scatter", "candlestick", "depth"]).optional().describe("Chart shape to generate. Defaults from source."),
1201
+ closeField: fieldName.optional().describe("Candlestick close-value field."),
1202
+ highField: fieldName.optional().describe("Candlestick high-value field."),
1203
+ labelField: fieldName.optional().describe("Categorical label field for bar charts."),
1204
+ lowField: fieldName.optional().describe("Candlestick low-value field."),
1205
+ maxPoints: z__namespace.number().int().positive().max(options.maxRows).optional().describe("Maximum rows to include in the frontend spec; defaults to all provided rows."),
1206
+ openField: fieldName.optional().describe("Candlestick open-value field."),
1207
+ priceField: fieldName.optional().describe("Market-depth price field."),
1208
+ renderer: z__namespace.literal("vega-lite").optional().describe("Visualization spec renderer. Currently only vega-lite is generated."),
1209
+ rows: z__namespace.array(z__namespace.record(chartScalar)).min(1).max(options.maxRows).describe("Chart data rows copied from a bounded Bloomberg tool result."),
1210
+ seriesField: fieldName.optional().describe("Optional series/color field."),
1211
+ sideField: fieldName.optional().describe("Market-depth bid/ask side field."),
1212
+ sizeField: fieldName.optional().describe("Market-depth size field."),
1213
+ source: z__namespace.enum(["bdh", "bdib", "holdings", "depth", "rows"]).describe("Bloomberg result shape that produced rows."),
1214
+ title: nonEmptyString(options, "Chart title.").optional(),
1215
+ valueField: fieldName.optional().describe("Primary numeric value field."),
1216
+ xField: fieldName.optional().describe("X-axis field."),
1217
+ yFields: stringArray(options, "Numeric value fields to plot.").optional()
1218
+ }).strict();
1219
+ }
805
1220
 
806
1221
  // src/ext-tools.ts
807
1222
  function resultString(resolver, name, value) {
@@ -816,6 +1231,7 @@ var EXT_TOOL_DEFINITIONS = Object.freeze([
816
1231
  { create: extCdxWithResolver, name: "xbbg_ext_cdx" },
817
1232
  { create: extCurrencyWithResolver, name: "xbbg_ext_currency" },
818
1233
  { create: extBqlBuilderWithResolver, name: "xbbg_ext_bql_builder" },
1234
+ { create: extChartSpecWithResolver, name: "xbbg_ext_chart_spec" },
819
1235
  { create: extMarketSessionWithResolver, name: "xbbg_ext_market_session" },
820
1236
  { create: extYasOverridesWithResolver, name: "xbbg_ext_yas_overrides" },
821
1237
  { create: extConstantsWithResolver, name: "xbbg_ext_constants" },
@@ -825,6 +1241,24 @@ var EXT_TOOL_DEFINITIONS = Object.freeze([
825
1241
  var BLOOMBERG_EXT_TOOL_NAMES = Object.freeze(
826
1242
  EXT_TOOL_DEFINITIONS.map((definition) => definition.name)
827
1243
  );
1244
+ function extChartSpecWithResolver(resolver) {
1245
+ const name = "xbbg_ext_chart_spec";
1246
+ return createBloombergStructuredTool(
1247
+ async (input) => {
1248
+ try {
1249
+ return await Promise.resolve(resultString(resolver, name, createChartSpec(input)));
1250
+ } catch (error) {
1251
+ throwWithToolContext(name, error);
1252
+ }
1253
+ },
1254
+ {
1255
+ responseFormat: "content_and_artifact",
1256
+ description: EXT_CHART_SPEC_DESCRIPTION,
1257
+ name,
1258
+ schema: chartSpecSchema(resolver.options)
1259
+ }
1260
+ );
1261
+ }
828
1262
  function extTickerWithResolver(resolver) {
829
1263
  const name = "xbbg_ext_ticker";
830
1264
  return createBloombergStructuredTool(
@@ -1238,6 +1672,9 @@ function createExtColumnsTool(options = {}) {
1238
1672
  function createExtCalculateTool(options = {}) {
1239
1673
  return extCalculateWithResolver(createCoreResolver(options));
1240
1674
  }
1675
+ function createExtChartSpecTool(options = {}) {
1676
+ return extChartSpecWithResolver(createCoreResolver(options));
1677
+ }
1241
1678
  function createBloombergExtToolsForResolver(resolver) {
1242
1679
  return EXT_TOOL_DEFINITIONS.filter(
1243
1680
  (definition) => !isToolDisabled(resolver.options, definition.name)
@@ -1731,7 +2168,7 @@ function createPreferredsSchema(options) {
1731
2168
  ).describe(
1732
2169
  "The issuer's common equity ticker as '<TICKER> <MARKET_SECTOR>', never a preferred ('Pfd') ticker and never a guessed one. Resolve a supplied ISIN/CUSIP with xbbg_resolve_isins first."
1733
2170
  ),
1734
- fields: stringArray2(tool2, "fields", options.maxFields, options.maxStringChars, '["<FIELD>"]').optional().describe("Optional fields to include in the preferreds recipe result.")
2171
+ fields: z__namespace.array(nonEmptyString2(tool2, "fields", options.maxStringChars, '["<FIELD>"]')).max(options.maxFields, `${tool2}: fields can contain at most ${options.maxFields} values`).transform((fields) => fields.length === 0 ? void 0 : fields).optional().describe("Optional fields to include in the preferreds recipe result.")
1735
2172
  });
1736
2173
  }
1737
2174
  function createCorporateBondsSchema(options) {
@@ -2669,6 +3106,7 @@ exports.createEtfHoldingsTool = createEtfHoldingsTool;
2669
3106
  exports.createExtBqlBuilderTool = createExtBqlBuilderTool;
2670
3107
  exports.createExtCalculateTool = createExtCalculateTool;
2671
3108
  exports.createExtCdxTool = createExtCdxTool;
3109
+ exports.createExtChartSpecTool = createExtChartSpecTool;
2672
3110
  exports.createExtColumnsTool = createExtColumnsTool;
2673
3111
  exports.createExtConstantsTool = createExtConstantsTool;
2674
3112
  exports.createExtCurrencyTool = createExtCurrencyTool;