flowquery 1.0.8 → 1.0.9

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 (33) hide show
  1. package/dist/flowquery.min.js +1 -1
  2. package/dist/parsing/functions/function_factory.d.ts +1 -0
  3. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  4. package/dist/parsing/functions/function_factory.js +1 -0
  5. package/dist/parsing/functions/function_factory.js.map +1 -1
  6. package/dist/parsing/functions/keys.d.ts +7 -0
  7. package/dist/parsing/functions/keys.d.ts.map +1 -0
  8. package/dist/parsing/functions/keys.js +42 -0
  9. package/dist/parsing/functions/keys.js.map +1 -0
  10. package/docs/flowquery.min.js +1 -1
  11. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  12. package/misc/apps/RAG/package.json +3 -1
  13. package/misc/apps/RAG/src/components/AdaptiveCardRenderer.css +172 -0
  14. package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +312 -0
  15. package/misc/apps/RAG/src/components/ChatContainer.tsx +159 -112
  16. package/misc/apps/RAG/src/components/ChatInput.tsx +58 -44
  17. package/misc/apps/RAG/src/components/ChatMessage.tsx +186 -101
  18. package/misc/apps/RAG/src/components/FlowQueryAgent.ts +50 -6
  19. package/misc/apps/RAG/src/components/FlowQueryRunner.css +9 -0
  20. package/misc/apps/RAG/src/components/FlowQueryRunner.tsx +44 -5
  21. package/misc/apps/RAG/src/components/index.ts +4 -0
  22. package/misc/apps/RAG/src/plugins/index.ts +6 -4
  23. package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +1 -2
  24. package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +1 -2
  25. package/misc/apps/RAG/src/plugins/loaders/Form.ts +578 -0
  26. package/misc/apps/RAG/src/plugins/loaders/Llm.ts +1 -2
  27. package/misc/apps/RAG/src/plugins/loaders/MockData.ts +2 -4
  28. package/misc/apps/RAG/src/plugins/loaders/Table.ts +271 -0
  29. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +12 -0
  30. package/package.json +1 -1
  31. package/src/parsing/functions/function_factory.ts +1 -0
  32. package/src/parsing/functions/keys.ts +31 -0
  33. package/tests/compute/runner.test.ts +8 -0
@@ -1,7 +1,8 @@
1
- import React, { useState, useMemo } from 'react';
1
+ import React, { Component } from 'react';
2
2
  import { Body1, Spinner, Button, Tooltip } from '@fluentui/react-components';
3
3
  import { PersonFilled, BotFilled, Play16Regular } from '@fluentui/react-icons';
4
4
  import { FlowQueryRunner } from './FlowQueryRunner';
5
+ import { AdaptiveCardRenderer, isAdaptiveCard } from './AdaptiveCardRenderer';
5
6
  import './ChatMessage.css';
6
7
 
7
8
  export interface Message {
@@ -10,6 +11,10 @@ export interface Message {
10
11
  content: string;
11
12
  timestamp: Date;
12
13
  isStreaming?: boolean;
14
+ /**
15
+ * Optional Adaptive Card payload to render instead of or alongside text content
16
+ */
17
+ adaptiveCard?: Record<string, unknown>;
13
18
  }
14
19
 
15
20
  interface ChatMessageProps {
@@ -35,118 +40,198 @@ function extractFlowQueryBlocks(content: string): string[] {
35
40
  }
36
41
 
37
42
  /**
38
- * Renders message content with FlowQuery code blocks enhanced with run buttons.
43
+ * Extract JSON blocks from content and check if any are Adaptive Cards
39
44
  */
40
- const MessageContent: React.FC<{ content: string; isStreaming?: boolean }> = ({ content, isStreaming }) => {
41
- const [runnerQuery, setRunnerQuery] = useState<string | null>(null);
45
+ function extractAdaptiveCards(content: string): Record<string, unknown>[] {
46
+ const cards: Record<string, unknown>[] = [];
42
47
 
43
- const flowQueryBlocks = useMemo(() => extractFlowQueryBlocks(content), [content]);
48
+ // Look for ```json blocks that might contain Adaptive Cards
49
+ const jsonRegex = /```json\n([\s\S]*?)```/gi;
50
+ let match;
44
51
 
45
- // If there are no FlowQuery blocks, render plain content
46
- if (flowQueryBlocks.length === 0) {
47
- return (
48
- <>
49
- {content}
50
- {isStreaming && <Spinner size="tiny" className="streaming-indicator" />}
51
- </>
52
- );
52
+ while ((match = jsonRegex.exec(content)) !== null) {
53
+ try {
54
+ const parsed = JSON.parse(match[1]);
55
+ if (isAdaptiveCard(parsed)) {
56
+ cards.push(parsed);
57
+ }
58
+ } catch {
59
+ // Not valid JSON, skip
60
+ }
53
61
  }
54
62
 
55
- // Split content by FlowQuery code blocks and render with buttons
56
- const parts: React.ReactNode[] = [];
57
- let lastIndex = 0;
58
- const regex = /```flowquery\n([\s\S]*?)```/gi;
59
- let match;
60
- let partIndex = 0;
61
-
62
- while ((match = regex.exec(content)) !== null) {
63
- // Add text before the code block
64
- if (match.index > lastIndex) {
63
+ return cards;
64
+ }
65
+
66
+ interface MessageContentProps {
67
+ content: string;
68
+ isStreaming?: boolean;
69
+ adaptiveCard?: Record<string, unknown>;
70
+ }
71
+
72
+ interface MessageContentState {
73
+ runnerQuery: string | null;
74
+ }
75
+
76
+ /**
77
+ * Renders message content with FlowQuery code blocks enhanced with run buttons,
78
+ * and optionally renders Adaptive Cards.
79
+ */
80
+ class MessageContent extends Component<MessageContentProps, MessageContentState> {
81
+ constructor(props: MessageContentProps) {
82
+ super(props);
83
+ this.state = {
84
+ runnerQuery: null
85
+ };
86
+ }
87
+
88
+ private setRunnerQuery = (query: string | null) => {
89
+ this.setState({ runnerQuery: query });
90
+ };
91
+
92
+ private getFlowQueryBlocks(): string[] {
93
+ return extractFlowQueryBlocks(this.props.content);
94
+ }
95
+
96
+ private getEmbeddedAdaptiveCards(): Record<string, unknown>[] {
97
+ return extractAdaptiveCards(this.props.content);
98
+ }
99
+
100
+ private getAllAdaptiveCards(): Record<string, unknown>[] {
101
+ const cards: Record<string, unknown>[] = [];
102
+ if (this.props.adaptiveCard) {
103
+ cards.push(this.props.adaptiveCard);
104
+ }
105
+ cards.push(...this.getEmbeddedAdaptiveCards());
106
+ return cards;
107
+ }
108
+
109
+ render() {
110
+ const { content, isStreaming } = this.props;
111
+ const { runnerQuery } = this.state;
112
+
113
+ const flowQueryBlocks = this.getFlowQueryBlocks();
114
+ const allAdaptiveCards = this.getAllAdaptiveCards();
115
+
116
+ // If there are no FlowQuery blocks, render plain content (possibly with Adaptive Cards)
117
+ if (flowQueryBlocks.length === 0) {
118
+ return (
119
+ <>
120
+ {content}
121
+ {allAdaptiveCards.map((card, index) => (
122
+ <AdaptiveCardRenderer key={`card-${index}`} card={card} />
123
+ ))}
124
+ {isStreaming && <Spinner size="tiny" className="streaming-indicator" />}
125
+ </>
126
+ );
127
+ }
128
+
129
+ // Split content by FlowQuery code blocks and render with buttons
130
+ const parts: React.ReactNode[] = [];
131
+ let lastIndex = 0;
132
+ const regex = /```flowquery\n([\s\S]*?)```/gi;
133
+ let match;
134
+ let partIndex = 0;
135
+
136
+ while ((match = regex.exec(content)) !== null) {
137
+ // Add text before the code block
138
+ if (match.index > lastIndex) {
139
+ parts.push(
140
+ <span key={`text-${partIndex}`}>
141
+ {content.slice(lastIndex, match.index)}
142
+ </span>
143
+ );
144
+ }
145
+
146
+ const query = match[1]?.trim() || '';
147
+
148
+ // Add the code block with a run button and </> link
149
+ parts.push(
150
+ <div key={`code-${partIndex}`} className="flowquery-code-block">
151
+ <div className="flowquery-code-header">
152
+ <span className="flowquery-code-label">flowquery</span>
153
+ <div className="flowquery-code-actions">
154
+ <Tooltip content="Run in FlowQuery Runner" relationship="label">
155
+ <Button
156
+ appearance="subtle"
157
+ size="small"
158
+ icon={<Play16Regular />}
159
+ className="flowquery-run-button"
160
+ onClick={() => this.setRunnerQuery(query)}
161
+ >
162
+ Run
163
+ </Button>
164
+ </Tooltip>
165
+ </div>
166
+ </div>
167
+ <pre className="flowquery-code-content">
168
+ <code>{query}</code>
169
+ </pre>
170
+ </div>
171
+ );
172
+
173
+ lastIndex = match.index + match[0].length;
174
+ partIndex++;
175
+ }
176
+
177
+ // Add remaining text after the last code block
178
+ if (lastIndex < content.length) {
65
179
  parts.push(
66
180
  <span key={`text-${partIndex}`}>
67
- {content.slice(lastIndex, match.index)}
181
+ {content.slice(lastIndex)}
68
182
  </span>
69
183
  );
70
184
  }
71
-
72
- const query = match[1]?.trim() || '';
73
-
74
- // Add the code block with a run button and </> link
75
- parts.push(
76
- <div key={`code-${partIndex}`} className="flowquery-code-block">
77
- <div className="flowquery-code-header">
78
- <span className="flowquery-code-label">flowquery</span>
79
- <div className="flowquery-code-actions">
80
- <Tooltip content="Run in FlowQuery Runner" relationship="label">
81
- <Button
82
- appearance="subtle"
83
- size="small"
84
- icon={<Play16Regular />}
85
- className="flowquery-run-button"
86
- onClick={() => setRunnerQuery(query)}
87
- >
88
- Run
89
- </Button>
90
- </Tooltip>
91
- </div>
92
- </div>
93
- <pre className="flowquery-code-content">
94
- <code>{query}</code>
95
- </pre>
96
- </div>
97
- );
98
-
99
- lastIndex = match.index + match[0].length;
100
- partIndex++;
101
- }
102
-
103
- // Add remaining text after the last code block
104
- if (lastIndex < content.length) {
105
- parts.push(
106
- <span key={`text-${partIndex}`}>
107
- {content.slice(lastIndex)}
108
- </span>
185
+
186
+ return (
187
+ <>
188
+ {parts}
189
+ {allAdaptiveCards.map((card, index) => (
190
+ <AdaptiveCardRenderer key={`card-${index}`} card={card} />
191
+ ))}
192
+ {isStreaming && <Spinner size="tiny" className="streaming-indicator" />}
193
+ {runnerQuery !== null && (
194
+ <FlowQueryRunner
195
+ initialQuery={runnerQuery}
196
+ open={true}
197
+ onOpenChange={(open) => {
198
+ if (!open) this.setRunnerQuery(null);
199
+ }}
200
+ />
201
+ )}
202
+ </>
109
203
  );
110
204
  }
111
-
112
- return (
113
- <>
114
- {parts}
115
- {isStreaming && <Spinner size="tiny" className="streaming-indicator" />}
116
- {runnerQuery !== null && (
117
- <FlowQueryRunner
118
- initialQuery={runnerQuery}
119
- open={true}
120
- onOpenChange={(open) => {
121
- if (!open) setRunnerQuery(null);
122
- }}
123
- />
124
- )}
125
- </>
126
- );
127
- };
128
-
129
- export const ChatMessage: React.FC<ChatMessageProps> = ({ message }) => {
130
- const isUser = message.role === 'user';
131
-
132
- return (
133
- <div className={`chat-message ${isUser ? 'chat-message-user' : 'chat-message-assistant'}`}>
134
- <div className="chat-message-avatar">
135
- {isUser ? <PersonFilled /> : <BotFilled />}
136
- </div>
137
- <div className="chat-message-content">
138
- <div className="chat-message-header">
139
- <Body1 className="chat-message-role">
140
- {isUser ? 'You' : 'Assistant'}
141
- </Body1>
142
- <span className="chat-message-time">
143
- {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
144
- </span>
205
+ }
206
+
207
+ export class ChatMessage extends Component<ChatMessageProps> {
208
+ render() {
209
+ const { message } = this.props;
210
+ const isUser = message.role === 'user';
211
+
212
+ return (
213
+ <div className={`chat-message ${isUser ? 'chat-message-user' : 'chat-message-assistant'}`}>
214
+ <div className="chat-message-avatar">
215
+ {isUser ? <PersonFilled /> : <BotFilled />}
145
216
  </div>
146
- <div className="chat-message-text">
147
- <MessageContent content={message.content} isStreaming={message.isStreaming} />
217
+ <div className="chat-message-content">
218
+ <div className="chat-message-header">
219
+ <Body1 className="chat-message-role">
220
+ {isUser ? 'You' : 'Assistant'}
221
+ </Body1>
222
+ <span className="chat-message-time">
223
+ {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
224
+ </span>
225
+ </div>
226
+ <div className="chat-message-text">
227
+ <MessageContent
228
+ content={message.content}
229
+ isStreaming={message.isStreaming}
230
+ adaptiveCard={message.adaptiveCard}
231
+ />
232
+ </div>
148
233
  </div>
149
234
  </div>
150
- </div>
151
- );
152
- };
235
+ );
236
+ }
237
+ }
@@ -11,6 +11,7 @@
11
11
  import { llm, llmStream, LlmOptions, LlmResponse } from '../plugins/loaders/Llm';
12
12
  import { FlowQueryExecutor, FlowQueryExecutionResult } from '../utils/FlowQueryExecutor';
13
13
  import { extractFlowQuery, FlowQueryExtraction } from '../utils/FlowQueryExtractor';
14
+ import { isAdaptiveCard } from './AdaptiveCardRenderer';
14
15
 
15
16
  // Shared executor instance
16
17
  const flowQueryExecutor = new FlowQueryExecutor();
@@ -222,7 +223,7 @@ export async function processQuery(
222
223
  export async function* processQueryStream(
223
224
  userQuery: string,
224
225
  options: FlowQueryAgentOptions
225
- ): AsyncGenerator<{ chunk: string; step: AgentStep['type']; done: boolean; steps?: AgentStep[] }, void, unknown> {
226
+ ): AsyncGenerator<{ chunk: string; step: AgentStep['type']; done: boolean; steps?: AgentStep[]; adaptiveCard?: Record<string, unknown> }, void, unknown> {
226
227
  const steps: AgentStep[] = [];
227
228
  const { systemPrompt, llmOptions = {}, conversationHistory = [], showIntermediateSteps = true } = options;
228
229
 
@@ -290,11 +291,15 @@ export async function* processQueryStream(
290
291
  return;
291
292
  }
292
293
 
294
+ // Check if the result contains an Adaptive Card
295
+ const adaptiveCard = extractAdaptiveCardFromResults(executionResult.results);
296
+
293
297
  // Step 4: Stream the interpretation
294
298
  const interpretationPrompt = buildInterpretationPrompt(
295
299
  userQuery,
296
300
  extraction.query!,
297
- executionResult
301
+ executionResult,
302
+ !!adaptiveCard
298
303
  );
299
304
 
300
305
  let interpretationContent = '';
@@ -317,7 +322,7 @@ export async function* processQueryStream(
317
322
  timestamp: new Date(),
318
323
  });
319
324
 
320
- yield { chunk: '', step: 'interpretation', done: true, steps };
325
+ yield { chunk: '', step: 'interpretation', done: true, steps, adaptiveCard };
321
326
  } catch (error) {
322
327
  const errorMessage = error instanceof Error ? error.message : String(error);
323
328
  yield {
@@ -329,18 +334,49 @@ export async function* processQueryStream(
329
334
  }
330
335
  }
331
336
 
337
+ /**
338
+ * Extract an Adaptive Card from the execution results.
339
+ * Checks if any result is an Adaptive Card (type: "AdaptiveCard") and returns it.
340
+ * Searches for Adaptive Cards at the top level or within any property of result objects.
341
+ */
342
+ function extractAdaptiveCardFromResults(results: unknown[] | undefined): Record<string, unknown> | undefined {
343
+ if (!results || !Array.isArray(results)) {
344
+ return undefined;
345
+ }
346
+
347
+ for (const result of results) {
348
+ // Check if the result itself is an Adaptive Card
349
+ if (isAdaptiveCard(result)) {
350
+ return result;
351
+ }
352
+
353
+ // Check if any property of the result object is an Adaptive Card
354
+ if (typeof result === 'object' && result !== null) {
355
+ const obj = result as Record<string, unknown>;
356
+ for (const value of Object.values(obj)) {
357
+ if (isAdaptiveCard(value)) {
358
+ return value as Record<string, unknown>;
359
+ }
360
+ }
361
+ }
362
+ }
363
+
364
+ return undefined;
365
+ }
366
+
332
367
  /**
333
368
  * Build the prompt for the interpretation phase.
334
369
  */
335
370
  function buildInterpretationPrompt(
336
371
  originalQuery: string,
337
372
  flowQuery: string,
338
- executionResult: FlowQueryExecutionResult
373
+ executionResult: FlowQueryExecutionResult,
374
+ hasAdaptiveCard: boolean = false
339
375
  ): string {
340
376
  const resultsJson = JSON.stringify(executionResult.results, null, 2);
341
377
  const resultCount = executionResult.results?.length || 0;
342
378
 
343
- return `The user asked: "${originalQuery}"
379
+ let prompt = `The user asked: "${originalQuery}"
344
380
 
345
381
  This was translated to the following FlowQuery:
346
382
  \`\`\`flowquery
@@ -353,7 +389,15 @@ The query executed successfully in ${executionResult.executionTime.toFixed(2)}ms
353
389
  ${resultsJson}
354
390
  \`\`\`
355
391
 
356
- Please interpret these results and provide a helpful response to the user's original question.`;
392
+ `;
393
+
394
+ if (hasAdaptiveCard) {
395
+ prompt += `The result is an Adaptive Card that will be rendered automatically in the UI. Please provide a brief introduction or context for the data shown in the card, but do NOT recreate the table or list the data in your response since the card will display it visually.`;
396
+ } else {
397
+ prompt += `Please interpret these results and provide a helpful response to the user's original question.`;
398
+ }
399
+
400
+ return prompt;
357
401
  }
358
402
 
359
403
  /**
@@ -102,3 +102,12 @@
102
102
  font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
103
103
  font-size: 12px;
104
104
  }
105
+
106
+ .flowquery-adaptive-card-results {
107
+ max-height: 400px;
108
+ overflow: auto;
109
+ border: 1px solid #e0e0e0;
110
+ border-radius: 4px;
111
+ padding: 8px;
112
+ background-color: #ffffff;
113
+ }
@@ -30,10 +30,40 @@ import {
30
30
  Checkmark24Regular,
31
31
  } from '@fluentui/react-icons';
32
32
  import { FlowQueryExecutor, FlowQueryExecutionResult } from '../utils/FlowQueryExecutor';
33
+ import { AdaptiveCardRenderer, isAdaptiveCard } from './AdaptiveCardRenderer';
33
34
  import './FlowQueryRunner.css';
34
35
 
35
36
  const flowQueryExecutor = new FlowQueryExecutor();
36
37
 
38
+ /**
39
+ * Extract an Adaptive Card from the execution results.
40
+ * Searches for Adaptive Cards at the top level or within any property of result objects.
41
+ */
42
+ function extractAdaptiveCardFromResults(results: unknown[] | undefined): Record<string, unknown> | undefined {
43
+ if (!results || !Array.isArray(results)) {
44
+ return undefined;
45
+ }
46
+
47
+ for (const result of results) {
48
+ // Check if the result itself is an Adaptive Card
49
+ if (isAdaptiveCard(result)) {
50
+ return result;
51
+ }
52
+
53
+ // Check if any property of the result object is an Adaptive Card
54
+ if (typeof result === 'object' && result !== null) {
55
+ const obj = result as Record<string, unknown>;
56
+ for (const value of Object.values(obj)) {
57
+ if (isAdaptiveCard(value)) {
58
+ return value as Record<string, unknown>;
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ return undefined;
65
+ }
66
+
37
67
  interface FlowQueryRunnerProps {
38
68
  /** Initial query to pre-populate the input */
39
69
  initialQuery?: string;
@@ -192,6 +222,9 @@ export class FlowQueryRunner extends Component<FlowQueryRunnerProps, FlowQueryRu
192
222
 
193
223
  const resultCount = result.results?.length || 0;
194
224
  const resultsJson = JSON.stringify(result.results, null, 2);
225
+
226
+ // Check if the result contains an Adaptive Card
227
+ const adaptiveCard = extractAdaptiveCardFromResults(result.results);
195
228
 
196
229
  return (
197
230
  <div className="flowquery-results-container">
@@ -214,11 +247,17 @@ export class FlowQueryRunner extends Component<FlowQueryRunnerProps, FlowQueryRu
214
247
  />
215
248
  </Tooltip>
216
249
  </div>
217
- <div className="flowquery-results">
218
- <pre className="flowquery-results-content">
219
- {resultsJson}
220
- </pre>
221
- </div>
250
+ {adaptiveCard ? (
251
+ <div className="flowquery-adaptive-card-results">
252
+ <AdaptiveCardRenderer card={adaptiveCard} />
253
+ </div>
254
+ ) : (
255
+ <div className="flowquery-results">
256
+ <pre className="flowquery-results-content">
257
+ {resultsJson}
258
+ </pre>
259
+ </div>
260
+ )}
222
261
  </div>
223
262
  );
224
263
  }
@@ -7,9 +7,13 @@ export { ApiKeySettings } from './ApiKeySettings';
7
7
  // FlowQuery Runner
8
8
  export { FlowQueryRunner } from './FlowQueryRunner';
9
9
 
10
+ // Adaptive Card Renderer
11
+ export { AdaptiveCardRenderer, isAdaptiveCard } from './AdaptiveCardRenderer';
12
+
10
13
  // FlowQuery Agent
11
14
  export { processQuery, processQueryStream } from './FlowQueryAgent';
12
15
  export type { AgentStep, AgentResult, FlowQueryAgentOptions, AgentStreamCallback } from './FlowQueryAgent';
13
16
 
14
17
  // Types
15
18
  export type { Message } from './ChatMessage';
19
+ export type { AdaptiveCardRendererProps } from './AdaptiveCardRenderer';
@@ -3,22 +3,24 @@
3
3
  *
4
4
  * To add a new plugin:
5
5
  * 1. Create a new file in the `loaders/` directory
6
- * 2. Add the @AsyncProviderDef decorator to your loader class
6
+ * 2. Add the @FunctionDef decorator with category: 'async' to your loader class
7
7
  * 3. Import the class in this file (the decorator auto-registers with FlowQuery)
8
8
  */
9
9
 
10
10
  import FlowQuery from 'flowquery';
11
11
  import { FunctionMetadata } from 'flowquery/extensibility';
12
12
 
13
- // Import loader classes - the @AsyncProviderDef decorator auto-registers them with FlowQuery
13
+ // Import loader classes - the @FunctionDef decorator auto-registers them with FlowQuery
14
14
  import './loaders/FetchJson';
15
15
  import './loaders/CatFacts';
16
16
  import './loaders/MockData';
17
17
  import './loaders/Llm';
18
+ import './loaders/Table';
19
+ import './loaders/Form';
18
20
 
19
21
  /**
20
22
  * Initialize plugins.
21
- * Plugins are auto-registered via @AsyncProviderDef decorators when imported.
23
+ * Plugins are auto-registered via @FunctionDef decorators when imported.
22
24
  * This function just logs the registered plugins for debugging.
23
25
  */
24
26
  export function initializePlugins(): void {
@@ -60,7 +62,7 @@ export async function getAvailableLoaders(): Promise<FunctionMetadata[]> {
60
62
  }
61
63
 
62
64
  // Re-export types for external use
63
- export type { FunctionMetadata, FunctionDefOptions, ParameterSchema, OutputSchema, AsyncDataProvider } from 'flowquery/extensibility';
65
+ export type { FunctionMetadata, FunctionDefOptions, ParameterSchema, OutputSchema } from 'flowquery/extensibility';
64
66
  export { FunctionDef } from 'flowquery/extensibility';
65
67
 
66
68
  // Re-export standalone loader functions for use outside of FlowQuery
@@ -14,9 +14,8 @@ const CAT_FACTS_API = 'https://catfact.ninja/facts';
14
14
  * CatFacts loader class - fetches random cat facts from the Cat Facts API.
15
15
  */
16
16
  @FunctionDef({
17
- isAsyncProvider: true,
18
17
  description: 'Fetches random cat facts from the Cat Facts API (catfact.ninja)',
19
- category: 'examples',
18
+ category: 'async',
20
19
  parameters: [
21
20
  {
22
21
  name: 'count',
@@ -12,9 +12,8 @@ import { FunctionDef } from 'flowquery/extensibility';
12
12
  * FetchJson loader class - fetches JSON data from a URL and yields items.
13
13
  */
14
14
  @FunctionDef({
15
- isAsyncProvider: true,
16
15
  description: 'Fetches JSON data from a URL. If the response is an array, yields each item individually.',
17
- category: 'data',
16
+ category: 'async',
18
17
  parameters: [
19
18
  {
20
19
  name: 'url',