@t2000/engine 0.46.6 → 0.46.7

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/index.d.ts CHANGED
@@ -922,6 +922,32 @@ declare class QueryEngine {
922
922
  reset(): void;
923
923
  getGuardEvents(): readonly GuardEvent[];
924
924
  loadMessages(messages: Message[]): void;
925
+ /**
926
+ * [v0.46.7] Run a read-only tool out-of-band, using the engine's tool
927
+ * registry and ToolContext. Used by hosts to deterministically pre-dispatch
928
+ * tools based on user-message intent (e.g. always call `balance_check` when
929
+ * the user says "what's my net worth?", regardless of whether the LLM would
930
+ * have otherwise re-called it).
931
+ *
932
+ * The host is responsible for:
933
+ * - Streaming the synthetic `tool_start` + `tool_result` events to the UI
934
+ * (so cards render as if the LLM had called the tool).
935
+ * - Appending matching `tool_use` + `tool_result` ContentBlocks to the
936
+ * engine's message history via `loadMessages([...getMessages(), ...synth])`
937
+ * BEFORE calling `submitMessage`, so the LLM sees the fresh data and
938
+ * doesn't re-call.
939
+ *
940
+ * Throws if the tool isn't registered, isn't read-only, or fails input
941
+ * validation. Tool execution errors are returned as `{ data, isError: true }`
942
+ * for the caller to handle (typically: skip the injection so the LLM falls
943
+ * back to its normal flow).
944
+ */
945
+ invokeReadTool(toolName: string, input: unknown, options?: {
946
+ signal?: AbortSignal;
947
+ }): Promise<{
948
+ data: unknown;
949
+ isError: boolean;
950
+ }>;
925
951
  setServerPositions(data: EngineConfig['serverPositions']): void;
926
952
  getUsage(): CostSnapshot;
927
953
  /**
package/dist/index.js CHANGED
@@ -1710,17 +1710,31 @@ async function fetchCatalog() {
1710
1710
  catalogCache = { data, ts: Date.now() };
1711
1711
  return data;
1712
1712
  }
1713
+ function renderServices(catalog) {
1714
+ return catalog.map((s) => ({
1715
+ id: s.id,
1716
+ name: s.name,
1717
+ description: s.description,
1718
+ categories: s.categories,
1719
+ endpoints: s.endpoints.map((e) => ({
1720
+ url: `${MPP_GATEWAY2}/${s.id}${e.path}`,
1721
+ method: e.method,
1722
+ description: e.description,
1723
+ price: `$${e.price}`
1724
+ }))
1725
+ }));
1726
+ }
1713
1727
  function matchesQuery(service, q) {
1714
1728
  const lower = q.toLowerCase();
1715
1729
  return service.id.toLowerCase().includes(lower) || service.name.toLowerCase().includes(lower) || service.description.toLowerCase().includes(lower) || service.categories.some((c) => c.toLowerCase().includes(lower)) || service.endpoints.some((e) => e.description.toLowerCase().includes(lower));
1716
1730
  }
1717
1731
  var mppServicesTool = buildTool({
1718
1732
  name: "mpp_services",
1719
- description: 'Discover available MPP gateway services. Returns service names, descriptions, endpoints with required parameters, and pricing. Use BEFORE calling pay_api. Modes: pass `query` for keyword search, `category` to filter by category, or `mode: "full"` to fetch the ENTIRE catalog in one card (for "show me all MPP services" / "full catalog" requests \u2014 never enumerate per category in a loop). Calling with no args returns a category summary so you can narrow.',
1733
+ description: 'Discover available MPP gateway services. Returns service names, descriptions, endpoints with required parameters, and pricing. Use BEFORE calling pay_api. With no args, returns the FULL catalog as a single card (default behavior \u2014 covers "show me available MPP services", "what services exist", "show me all MPP services"). Use `query` to keyword-search a specific need ("translate", "weather", "postcard"). Use `category` to filter to one category. Use `mode: "summary"` only if you explicitly want a category-counts overview without the full list.',
1720
1734
  inputSchema: z.object({
1721
- query: z.string().optional().describe('Filter by keyword (e.g. "postcard", "translate", "weather").'),
1722
- category: z.string().optional().describe('Filter by category exactly (e.g. "weather", "image"). See category summary returned when called without filters.'),
1723
- mode: z.enum(["summary", "full"]).optional().describe('"full" returns the entire catalog in a single card \u2014 use this for "show me all MPP services" / "full catalog" requests instead of looping per category. Default is "summary" (category counts only when no filter is supplied).')
1735
+ query: z.string().optional().describe('Filter by keyword (e.g. "postcard", "translate", "weather"). Returns matching services in one card.'),
1736
+ category: z.string().optional().describe('Filter by category exactly (e.g. "weather", "image"). Use mode:"summary" first if you need to see the category list.'),
1737
+ mode: z.enum(["summary", "full"]).optional().describe('"full" (default) returns the entire catalog in one card. "summary" returns category counts only \u2014 use this only when the user explicitly asks for a category overview.')
1724
1738
  }),
1725
1739
  jsonSchema: {
1726
1740
  type: "object",
@@ -1736,7 +1750,7 @@ var mppServicesTool = buildTool({
1736
1750
  mode: {
1737
1751
  type: "string",
1738
1752
  enum: ["summary", "full"],
1739
- description: '"full" returns the entire catalog in one card. Use for "show me all" requests.'
1753
+ description: '"full" (default) returns the entire catalog in one card. "summary" returns category counts only.'
1740
1754
  }
1741
1755
  },
1742
1756
  required: []
@@ -1748,25 +1762,14 @@ var mppServicesTool = buildTool({
1748
1762
  maxResultSizeChars: 12e3,
1749
1763
  async call(input) {
1750
1764
  const catalog = await fetchCatalog();
1751
- if (input.mode === "full") {
1752
- const services2 = catalog.map((s) => ({
1753
- id: s.id,
1754
- name: s.name,
1755
- description: s.description,
1756
- categories: s.categories,
1757
- endpoints: s.endpoints.map((e) => ({
1758
- url: `${MPP_GATEWAY2}/${s.id}${e.path}`,
1759
- method: e.method,
1760
- description: e.description,
1761
- price: `$${e.price}`
1762
- }))
1763
- }));
1765
+ if (input.mode !== "summary" && !input.query && !input.category) {
1766
+ const services2 = renderServices(catalog);
1764
1767
  return {
1765
1768
  data: { services: services2, total: services2.length, mode: "full" },
1766
1769
  displayText: `Full MPP catalog: ${services2.length} services.`
1767
1770
  };
1768
1771
  }
1769
- if (!input.query && !input.category) {
1772
+ if (input.mode === "summary" && !input.query && !input.category) {
1770
1773
  const counts = /* @__PURE__ */ new Map();
1771
1774
  for (const svc of catalog) {
1772
1775
  for (const cat of svc.categories) {
@@ -1777,14 +1780,14 @@ var mppServicesTool = buildTool({
1777
1780
  return {
1778
1781
  data: {
1779
1782
  _refine: {
1780
- reason: 'MPP catalog has many services \u2014 pick a category, supply a query, or pass mode:"full" to fetch everything.',
1783
+ reason: 'Category summary (mode:"summary"). Re-call with a category or omit mode for the full catalog.',
1781
1784
  suggestedParams: { category: categories[0]?.category ?? "weather" },
1782
1785
  allModes: ["summary", "full"]
1783
1786
  },
1784
1787
  categories,
1785
1788
  totalServices: catalog.length
1786
1789
  },
1787
- displayText: `${catalog.length} services across ${categories.length} categories. Re-call with a category, query, or mode:"full".`
1790
+ displayText: `${catalog.length} services across ${categories.length} categories.`
1788
1791
  };
1789
1792
  }
1790
1793
  let filtered = catalog;
@@ -1795,18 +1798,7 @@ var mppServicesTool = buildTool({
1795
1798
  if (input.query) {
1796
1799
  filtered = filtered.filter((s) => matchesQuery(s, input.query));
1797
1800
  }
1798
- const services = filtered.map((s) => ({
1799
- id: s.id,
1800
- name: s.name,
1801
- description: s.description,
1802
- categories: s.categories,
1803
- endpoints: s.endpoints.map((e) => ({
1804
- url: `${MPP_GATEWAY2}/${s.id}${e.path}`,
1805
- method: e.method,
1806
- description: e.description,
1807
- price: `$${e.price}`
1808
- }))
1809
- }));
1801
+ const services = renderServices(filtered);
1810
1802
  const filterDesc = [
1811
1803
  input.query ? `query "${input.query}"` : null,
1812
1804
  input.category ? `category "${input.category}"` : null
@@ -4595,6 +4587,62 @@ var QueryEngine = class {
4595
4587
  loadMessages(messages) {
4596
4588
  this.messages = [...messages];
4597
4589
  }
4590
+ /**
4591
+ * [v0.46.7] Run a read-only tool out-of-band, using the engine's tool
4592
+ * registry and ToolContext. Used by hosts to deterministically pre-dispatch
4593
+ * tools based on user-message intent (e.g. always call `balance_check` when
4594
+ * the user says "what's my net worth?", regardless of whether the LLM would
4595
+ * have otherwise re-called it).
4596
+ *
4597
+ * The host is responsible for:
4598
+ * - Streaming the synthetic `tool_start` + `tool_result` events to the UI
4599
+ * (so cards render as if the LLM had called the tool).
4600
+ * - Appending matching `tool_use` + `tool_result` ContentBlocks to the
4601
+ * engine's message history via `loadMessages([...getMessages(), ...synth])`
4602
+ * BEFORE calling `submitMessage`, so the LLM sees the fresh data and
4603
+ * doesn't re-call.
4604
+ *
4605
+ * Throws if the tool isn't registered, isn't read-only, or fails input
4606
+ * validation. Tool execution errors are returned as `{ data, isError: true }`
4607
+ * for the caller to handle (typically: skip the injection so the LLM falls
4608
+ * back to its normal flow).
4609
+ */
4610
+ async invokeReadTool(toolName, input, options = {}) {
4611
+ const tool = findTool(this.tools, toolName);
4612
+ if (!tool) throw new Error(`invokeReadTool: tool not found: ${toolName}`);
4613
+ if (!tool.isReadOnly) {
4614
+ throw new Error(`invokeReadTool: tool is not read-only: ${toolName} (write tools must go through the permission gate)`);
4615
+ }
4616
+ const parsed = tool.inputSchema.safeParse(input);
4617
+ if (!parsed.success) {
4618
+ throw new Error(
4619
+ `invokeReadTool: invalid input for ${toolName}: ${parsed.error.issues.map((i) => i.message).join(", ")}`
4620
+ );
4621
+ }
4622
+ const signal = options.signal ?? new AbortController().signal;
4623
+ const context = {
4624
+ agent: this.agent,
4625
+ mcpManager: this.mcpManager,
4626
+ walletAddress: this.walletAddress,
4627
+ suiRpcUrl: this.suiRpcUrl,
4628
+ serverPositions: this.serverPositions,
4629
+ positionFetcher: this.positionFetcher,
4630
+ env: this.env,
4631
+ signal,
4632
+ priceCache: this.priceCache,
4633
+ permissionConfig: this.permissionConfig,
4634
+ sessionSpendUsd: this.sessionSpendUsd
4635
+ };
4636
+ try {
4637
+ const result = await tool.call(parsed.data, context);
4638
+ return { data: result.data, isError: false };
4639
+ } catch (err) {
4640
+ return {
4641
+ data: { error: err instanceof Error ? err.message : "Tool execution failed" },
4642
+ isError: true
4643
+ };
4644
+ }
4645
+ }
4598
4646
  setServerPositions(data) {
4599
4647
  this.serverPositions = data;
4600
4648
  }