@portel/photon-core 1.1.0 → 1.2.0
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/generator.d.ts +587 -107
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +313 -151
- package/dist/generator.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/generator.ts +779 -220
- package/src/index.ts +50 -13
package/dist/generator.d.ts
CHANGED
|
@@ -1,205 +1,685 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generator-based Tool Execution
|
|
2
|
+
* Generator-based Tool Execution with Ask/Emit Pattern
|
|
3
3
|
*
|
|
4
|
-
* Enables photon tools to use generator functions with `yield` for:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* - Streaming responses
|
|
8
|
-
* - Multi-step wizards
|
|
4
|
+
* Enables photon tools to use async generator functions with `yield` for:
|
|
5
|
+
* - Interactive user input (ask) - blocks until user responds
|
|
6
|
+
* - Real-time output (emit) - fire and forget, no response needed
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* ══════════════════════════════════════════════════════════════════════════════
|
|
9
|
+
* DESIGN PHILOSOPHY
|
|
10
|
+
* ══════════════════════════════════════════════════════════════════════════════
|
|
11
|
+
*
|
|
12
|
+
* The `ask` vs `emit` pattern provides instant clarity:
|
|
13
|
+
* - `ask` = "I need something FROM the user" (blocks, returns value)
|
|
14
|
+
* - `emit` = "I'm sending something TO the user" (non-blocking, void)
|
|
15
|
+
*
|
|
16
|
+
* This maps naturally to all runtime contexts:
|
|
17
|
+
*
|
|
18
|
+
* | Runtime | ask (input) | emit (output) |
|
|
19
|
+
* |------------|--------------------------|----------------------------|
|
|
20
|
+
* | REST API | Returns 202 + continue | Included in response or SSE|
|
|
21
|
+
* | WebSocket | Server request → client | Server push to client |
|
|
22
|
+
* | CLI | Readline prompt | Console output |
|
|
23
|
+
* | MCP | Elicitation dialog | Notification/logging |
|
|
24
|
+
* | Chatbot | Bot question → user reply| Status message, typing... |
|
|
25
|
+
*
|
|
26
|
+
* ══════════════════════════════════════════════════════════════════════════════
|
|
27
|
+
* REST API CONTINUATION PATTERN
|
|
28
|
+
* ══════════════════════════════════════════════════════════════════════════════
|
|
29
|
+
*
|
|
30
|
+
* When a generator yields `ask`, REST APIs can implement a continuation flow:
|
|
31
|
+
*
|
|
32
|
+
* ```
|
|
33
|
+
* POST /api/google-tv/connect
|
|
34
|
+
* Body: { ip: "192.168.1.100" }
|
|
35
|
+
*
|
|
36
|
+
* Response (202 Accepted):
|
|
37
|
+
* {
|
|
38
|
+
* "status": "awaiting_input",
|
|
39
|
+
* "continuation_id": "ctx_abc123",
|
|
40
|
+
* "ask": { "type": "text", "id": "pairing_code", "message": "Enter code:" },
|
|
41
|
+
* "continue": "/api/google-tv/connect/ctx_abc123"
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* POST /api/google-tv/connect/ctx_abc123
|
|
45
|
+
* Body: { "pairing_code": "123456" }
|
|
46
|
+
*
|
|
47
|
+
* Response (200 OK):
|
|
48
|
+
* { "status": "complete", "result": { "success": true } }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* ══════════════════════════════════════════════════════════════════════════════
|
|
52
|
+
* USAGE EXAMPLE
|
|
53
|
+
* ══════════════════════════════════════════════════════════════════════════════
|
|
15
54
|
*
|
|
16
|
-
* @example
|
|
17
55
|
* ```typescript
|
|
18
56
|
* async *connect(params: { ip: string }) {
|
|
19
|
-
*
|
|
57
|
+
* yield { emit: 'status', message: 'Connecting to TV...' };
|
|
20
58
|
*
|
|
59
|
+
* await this.startPairing(params.ip);
|
|
60
|
+
*
|
|
61
|
+
* yield { emit: 'progress', value: 0.3, message: 'Waiting for code...' };
|
|
62
|
+
*
|
|
63
|
+
* // Blocks until user provides input
|
|
21
64
|
* const code: string = yield {
|
|
22
|
-
*
|
|
23
|
-
*
|
|
65
|
+
* ask: 'text',
|
|
66
|
+
* id: 'pairing_code',
|
|
67
|
+
* message: 'Enter the 6-digit code shown on TV:',
|
|
68
|
+
* pattern: '^[0-9]{6}$',
|
|
69
|
+
* required: true
|
|
24
70
|
* };
|
|
25
71
|
*
|
|
72
|
+
* yield { emit: 'status', message: 'Verifying code...' };
|
|
73
|
+
*
|
|
26
74
|
* await this.sendCode(code);
|
|
27
|
-
*
|
|
75
|
+
*
|
|
76
|
+
* yield { emit: 'toast', message: 'Connected!', type: 'success' };
|
|
77
|
+
*
|
|
78
|
+
* return { success: true, paired: true };
|
|
28
79
|
* }
|
|
29
80
|
* ```
|
|
81
|
+
*
|
|
82
|
+
* @module generator
|
|
30
83
|
*/
|
|
31
84
|
/**
|
|
32
|
-
*
|
|
85
|
+
* Base properties shared by all ask yields
|
|
33
86
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
87
|
+
interface AskBase {
|
|
88
|
+
/**
|
|
89
|
+
* Unique identifier for this input.
|
|
90
|
+
* Used for:
|
|
91
|
+
* - REST API parameter mapping (pre-provided inputs)
|
|
92
|
+
* - Continuation token correlation
|
|
93
|
+
* - Form field identification
|
|
94
|
+
*
|
|
95
|
+
* Auto-generated if not provided (ask_0, ask_1, etc.)
|
|
96
|
+
*/
|
|
39
97
|
id?: string;
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
98
|
+
/**
|
|
99
|
+
* The prompt message shown to the user.
|
|
100
|
+
* Should be clear and actionable.
|
|
101
|
+
*/
|
|
102
|
+
message: string;
|
|
103
|
+
/**
|
|
104
|
+
* Whether this input is required.
|
|
105
|
+
* If false, user can skip/cancel.
|
|
106
|
+
* @default true
|
|
107
|
+
*/
|
|
43
108
|
required?: boolean;
|
|
44
109
|
}
|
|
45
110
|
/**
|
|
46
|
-
*
|
|
111
|
+
* Text input - single line string
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* const name: string = yield {
|
|
115
|
+
* ask: 'text',
|
|
116
|
+
* message: 'Enter your name:',
|
|
117
|
+
* default: 'Guest',
|
|
118
|
+
* placeholder: 'John Doe'
|
|
119
|
+
* };
|
|
120
|
+
*/
|
|
121
|
+
export interface AskText extends AskBase {
|
|
122
|
+
ask: 'text';
|
|
123
|
+
/** Default value if user submits empty */
|
|
124
|
+
default?: string;
|
|
125
|
+
/** Placeholder hint shown in input field */
|
|
126
|
+
placeholder?: string;
|
|
127
|
+
/** Regex pattern for validation */
|
|
128
|
+
pattern?: string;
|
|
129
|
+
/** Minimum length */
|
|
130
|
+
minLength?: number;
|
|
131
|
+
/** Maximum length */
|
|
132
|
+
maxLength?: number;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Password input - hidden/masked string
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const apiKey: string = yield {
|
|
139
|
+
* ask: 'password',
|
|
140
|
+
* message: 'Enter your API key:'
|
|
141
|
+
* };
|
|
47
142
|
*/
|
|
48
|
-
export interface
|
|
49
|
-
|
|
50
|
-
|
|
143
|
+
export interface AskPassword extends AskBase {
|
|
144
|
+
ask: 'password';
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Confirmation - yes/no boolean
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* const confirmed: boolean = yield {
|
|
151
|
+
* ask: 'confirm',
|
|
152
|
+
* message: 'Delete this file permanently?',
|
|
153
|
+
* dangerous: true
|
|
154
|
+
* };
|
|
155
|
+
*/
|
|
156
|
+
export interface AskConfirm extends AskBase {
|
|
157
|
+
ask: 'confirm';
|
|
158
|
+
/**
|
|
159
|
+
* Mark as dangerous/destructive action.
|
|
160
|
+
* UI may show warning styling (red button, confirmation dialog).
|
|
161
|
+
*/
|
|
51
162
|
dangerous?: boolean;
|
|
52
|
-
|
|
163
|
+
/** Default value if user just presses enter */
|
|
164
|
+
default?: boolean;
|
|
53
165
|
}
|
|
54
166
|
/**
|
|
55
|
-
* Selection from options
|
|
167
|
+
* Selection from predefined options
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* // Simple string options
|
|
171
|
+
* const env: string = yield {
|
|
172
|
+
* ask: 'select',
|
|
173
|
+
* message: 'Choose environment:',
|
|
174
|
+
* options: ['development', 'staging', 'production']
|
|
175
|
+
* };
|
|
176
|
+
*
|
|
177
|
+
* // Rich options with labels
|
|
178
|
+
* const region: string = yield {
|
|
179
|
+
* ask: 'select',
|
|
180
|
+
* message: 'Select region:',
|
|
181
|
+
* options: [
|
|
182
|
+
* { value: 'us-east-1', label: 'US East (N. Virginia)' },
|
|
183
|
+
* { value: 'eu-west-1', label: 'EU West (Ireland)' }
|
|
184
|
+
* ]
|
|
185
|
+
* };
|
|
186
|
+
*
|
|
187
|
+
* // Multi-select
|
|
188
|
+
* const features: string[] = yield {
|
|
189
|
+
* ask: 'select',
|
|
190
|
+
* message: 'Enable features:',
|
|
191
|
+
* options: ['auth', 'logging', 'metrics'],
|
|
192
|
+
* multi: true
|
|
193
|
+
* };
|
|
56
194
|
*/
|
|
57
|
-
export interface
|
|
58
|
-
|
|
195
|
+
export interface AskSelect extends AskBase {
|
|
196
|
+
ask: 'select';
|
|
197
|
+
/** Available options */
|
|
59
198
|
options: Array<string | {
|
|
60
199
|
value: string;
|
|
61
200
|
label: string;
|
|
201
|
+
description?: string;
|
|
62
202
|
}>;
|
|
63
|
-
/** Allow multiple
|
|
203
|
+
/** Allow selecting multiple options */
|
|
64
204
|
multi?: boolean;
|
|
65
|
-
|
|
205
|
+
/** Default selected value(s) */
|
|
206
|
+
default?: string | string[];
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Number input with optional constraints
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* const quantity: number = yield {
|
|
213
|
+
* ask: 'number',
|
|
214
|
+
* message: 'Enter quantity:',
|
|
215
|
+
* min: 1,
|
|
216
|
+
* max: 100,
|
|
217
|
+
* step: 1
|
|
218
|
+
* };
|
|
219
|
+
*/
|
|
220
|
+
export interface AskNumber extends AskBase {
|
|
221
|
+
ask: 'number';
|
|
222
|
+
/** Minimum value */
|
|
223
|
+
min?: number;
|
|
224
|
+
/** Maximum value */
|
|
225
|
+
max?: number;
|
|
226
|
+
/** Step increment */
|
|
227
|
+
step?: number;
|
|
228
|
+
/** Default value */
|
|
229
|
+
default?: number;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* File selection (for supported runtimes)
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* const file: FileInfo = yield {
|
|
236
|
+
* ask: 'file',
|
|
237
|
+
* message: 'Select a document:',
|
|
238
|
+
* accept: '.pdf,.doc,.docx',
|
|
239
|
+
* multiple: false
|
|
240
|
+
* };
|
|
241
|
+
*/
|
|
242
|
+
export interface AskFile extends AskBase {
|
|
243
|
+
ask: 'file';
|
|
244
|
+
/** Accepted file types (MIME types or extensions) */
|
|
245
|
+
accept?: string;
|
|
246
|
+
/** Allow multiple file selection */
|
|
247
|
+
multiple?: boolean;
|
|
66
248
|
}
|
|
67
249
|
/**
|
|
68
|
-
*
|
|
250
|
+
* Date/time selection
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* const date: string = yield {
|
|
254
|
+
* ask: 'date',
|
|
255
|
+
* message: 'Select delivery date:',
|
|
256
|
+
* min: '2024-01-01',
|
|
257
|
+
* max: '2024-12-31'
|
|
258
|
+
* };
|
|
259
|
+
*/
|
|
260
|
+
export interface AskDate extends AskBase {
|
|
261
|
+
ask: 'date';
|
|
262
|
+
/** Include time selection */
|
|
263
|
+
includeTime?: boolean;
|
|
264
|
+
/** Minimum date (ISO string) */
|
|
265
|
+
min?: string;
|
|
266
|
+
/** Maximum date (ISO string) */
|
|
267
|
+
max?: string;
|
|
268
|
+
/** Default value (ISO string) */
|
|
269
|
+
default?: string;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Union of all ask (input) yield types
|
|
273
|
+
*/
|
|
274
|
+
export type AskYield = AskText | AskPassword | AskConfirm | AskSelect | AskNumber | AskFile | AskDate;
|
|
275
|
+
/**
|
|
276
|
+
* Status message - general purpose user notification
|
|
277
|
+
*
|
|
278
|
+
* Use for: progress updates, step completions, informational messages
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* yield { emit: 'status', message: 'Connecting to server...' };
|
|
282
|
+
* yield { emit: 'status', message: 'Upload complete!', type: 'success' };
|
|
283
|
+
*/
|
|
284
|
+
export interface EmitStatus {
|
|
285
|
+
emit: 'status';
|
|
286
|
+
/** Message to display */
|
|
287
|
+
message: string;
|
|
288
|
+
/** Message type for styling */
|
|
289
|
+
type?: 'info' | 'success' | 'warning' | 'error';
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Progress update - for long-running operations
|
|
293
|
+
*
|
|
294
|
+
* Runtimes may display as: progress bar, percentage, spinner
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* yield { emit: 'progress', value: 0.0, message: 'Starting...' };
|
|
298
|
+
* yield { emit: 'progress', value: 0.5, message: 'Halfway there...' };
|
|
299
|
+
* yield { emit: 'progress', value: 1.0, message: 'Complete!' };
|
|
69
300
|
*/
|
|
70
|
-
export interface
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
301
|
+
export interface EmitProgress {
|
|
302
|
+
emit: 'progress';
|
|
303
|
+
/** Progress value from 0 to 1 (0% to 100%) */
|
|
304
|
+
value: number;
|
|
305
|
+
/** Optional status message */
|
|
306
|
+
message?: string;
|
|
307
|
+
/** Additional metadata */
|
|
308
|
+
meta?: Record<string, any>;
|
|
75
309
|
}
|
|
76
310
|
/**
|
|
77
|
-
*
|
|
311
|
+
* Streaming data - for chunked responses
|
|
312
|
+
*
|
|
313
|
+
* Use for: streaming text, large file transfers, real-time data
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* for await (const chunk of aiStream) {
|
|
317
|
+
* yield { emit: 'stream', data: chunk.text };
|
|
318
|
+
* }
|
|
319
|
+
* yield { emit: 'stream', data: '', final: true };
|
|
78
320
|
*/
|
|
79
|
-
export interface
|
|
80
|
-
|
|
321
|
+
export interface EmitStream {
|
|
322
|
+
emit: 'stream';
|
|
323
|
+
/** Data chunk to send */
|
|
324
|
+
data: any;
|
|
81
325
|
/** Whether this is the final chunk */
|
|
82
326
|
final?: boolean;
|
|
327
|
+
/** Content type hint */
|
|
328
|
+
contentType?: string;
|
|
83
329
|
}
|
|
84
330
|
/**
|
|
85
|
-
* Log
|
|
331
|
+
* Log message - for debugging/development
|
|
332
|
+
*
|
|
333
|
+
* May be hidden in production or routed to logging system
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* yield { emit: 'log', message: 'Processing item', level: 'debug', data: { id: 123 } };
|
|
86
337
|
*/
|
|
87
|
-
export interface
|
|
88
|
-
|
|
338
|
+
export interface EmitLog {
|
|
339
|
+
emit: 'log';
|
|
340
|
+
/** Log message */
|
|
341
|
+
message: string;
|
|
342
|
+
/** Log level */
|
|
89
343
|
level?: 'debug' | 'info' | 'warn' | 'error';
|
|
344
|
+
/** Additional structured data */
|
|
345
|
+
data?: Record<string, any>;
|
|
90
346
|
}
|
|
91
347
|
/**
|
|
92
|
-
*
|
|
348
|
+
* Toast notification - ephemeral popup message
|
|
349
|
+
*
|
|
350
|
+
* Use for: success confirmations, quick alerts, non-blocking notices
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* yield { emit: 'toast', message: 'Settings saved!', type: 'success' };
|
|
354
|
+
* yield { emit: 'toast', message: 'Connection lost', type: 'error', duration: 5000 };
|
|
93
355
|
*/
|
|
94
|
-
export
|
|
356
|
+
export interface EmitToast {
|
|
357
|
+
emit: 'toast';
|
|
358
|
+
/** Toast message */
|
|
359
|
+
message: string;
|
|
360
|
+
/** Toast type for styling */
|
|
361
|
+
type?: 'info' | 'success' | 'warning' | 'error';
|
|
362
|
+
/** Display duration in ms (0 = sticky) */
|
|
363
|
+
duration?: number;
|
|
364
|
+
}
|
|
95
365
|
/**
|
|
96
|
-
*
|
|
366
|
+
* Thinking indicator - for chatbot/AI contexts
|
|
367
|
+
*
|
|
368
|
+
* Shows user that processing is happening (typing dots, spinner)
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* yield { emit: 'thinking', active: true };
|
|
372
|
+
* const result = await this.heavyComputation();
|
|
373
|
+
* yield { emit: 'thinking', active: false };
|
|
97
374
|
*/
|
|
98
|
-
export
|
|
375
|
+
export interface EmitThinking {
|
|
376
|
+
emit: 'thinking';
|
|
377
|
+
/** Whether thinking indicator should be shown */
|
|
378
|
+
active: boolean;
|
|
379
|
+
}
|
|
99
380
|
/**
|
|
100
|
-
*
|
|
381
|
+
* Rich artifact - embedded content preview
|
|
382
|
+
*
|
|
383
|
+
* Use for: images, code blocks, documents, embeds
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* yield {
|
|
387
|
+
* emit: 'artifact',
|
|
388
|
+
* type: 'image',
|
|
389
|
+
* url: 'https://example.com/chart.png',
|
|
390
|
+
* title: 'Sales Chart Q4'
|
|
391
|
+
* };
|
|
392
|
+
*
|
|
393
|
+
* yield {
|
|
394
|
+
* emit: 'artifact',
|
|
395
|
+
* type: 'code',
|
|
396
|
+
* language: 'typescript',
|
|
397
|
+
* content: 'const x = 1;',
|
|
398
|
+
* title: 'Example'
|
|
399
|
+
* };
|
|
101
400
|
*/
|
|
102
|
-
export
|
|
401
|
+
export interface EmitArtifact {
|
|
402
|
+
emit: 'artifact';
|
|
403
|
+
/** Artifact type */
|
|
404
|
+
type: 'image' | 'code' | 'document' | 'embed' | 'json';
|
|
405
|
+
/** Title/label */
|
|
406
|
+
title?: string;
|
|
407
|
+
/** URL for external content */
|
|
408
|
+
url?: string;
|
|
409
|
+
/** Inline content */
|
|
410
|
+
content?: string;
|
|
411
|
+
/** Language hint for code */
|
|
412
|
+
language?: string;
|
|
413
|
+
/** MIME type hint */
|
|
414
|
+
mimeType?: string;
|
|
415
|
+
}
|
|
103
416
|
/**
|
|
104
|
-
*
|
|
417
|
+
* Union of all emit (output) yield types
|
|
105
418
|
*/
|
|
106
|
-
export
|
|
419
|
+
export type EmitYield = EmitStatus | EmitProgress | EmitStream | EmitLog | EmitToast | EmitThinking | EmitArtifact;
|
|
107
420
|
/**
|
|
108
|
-
*
|
|
421
|
+
* All possible yield types from a photon generator
|
|
109
422
|
*/
|
|
110
|
-
export
|
|
423
|
+
export type PhotonYield = AskYield | EmitYield;
|
|
111
424
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
425
|
+
* Check if yield is an ask (requires user input)
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* if (isAskYield(yielded)) {
|
|
429
|
+
* const userInput = await promptUser(yielded);
|
|
430
|
+
* generator.next(userInput);
|
|
431
|
+
* }
|
|
432
|
+
*/
|
|
433
|
+
export declare function isAskYield(y: PhotonYield): y is AskYield;
|
|
434
|
+
/**
|
|
435
|
+
* Check if yield is an emit (output only, no response needed)
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* if (isEmitYield(yielded)) {
|
|
439
|
+
* handleOutput(yielded);
|
|
440
|
+
* generator.next(); // Continue without value
|
|
441
|
+
* }
|
|
442
|
+
*/
|
|
443
|
+
export declare function isEmitYield(y: PhotonYield): y is EmitYield;
|
|
444
|
+
/**
|
|
445
|
+
* Get the type of an ask yield
|
|
446
|
+
*/
|
|
447
|
+
export declare function getAskType(y: AskYield): AskYield['ask'];
|
|
448
|
+
/**
|
|
449
|
+
* Get the type of an emit yield
|
|
450
|
+
*/
|
|
451
|
+
export declare function getEmitType(y: EmitYield): EmitYield['emit'];
|
|
452
|
+
/**
|
|
453
|
+
* Check if a function is an async generator function
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* if (isAsyncGeneratorFunction(method)) {
|
|
457
|
+
* const gen = method.call(instance, params);
|
|
458
|
+
* await executeGenerator(gen, config);
|
|
459
|
+
* }
|
|
460
|
+
*/
|
|
461
|
+
export declare function isAsyncGeneratorFunction(fn: any): fn is (...args: any[]) => AsyncGenerator;
|
|
462
|
+
/**
|
|
463
|
+
* Check if a value is an async generator instance (already invoked)
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* const result = method.call(instance, params);
|
|
467
|
+
* if (isAsyncGenerator(result)) {
|
|
468
|
+
* await executeGenerator(result, config);
|
|
469
|
+
* }
|
|
470
|
+
*/
|
|
471
|
+
export declare function isAsyncGenerator(obj: any): obj is AsyncGenerator;
|
|
472
|
+
/**
|
|
473
|
+
* Function that provides input for an ask yield.
|
|
474
|
+
*
|
|
475
|
+
* Runtimes implement this based on their capabilities:
|
|
476
|
+
* - CLI: readline prompts
|
|
477
|
+
* - MCP: elicitation dialogs
|
|
478
|
+
* - WebSocket: request/response messages
|
|
479
|
+
* - REST: throw NeedsInputError for continuation flow
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* const cliInputProvider: InputProvider = async (ask) => {
|
|
483
|
+
* if (ask.ask === 'text') return await readline(ask.message);
|
|
484
|
+
* if (ask.ask === 'confirm') return await confirm(ask.message);
|
|
485
|
+
* // ...
|
|
486
|
+
* };
|
|
114
487
|
*/
|
|
115
|
-
export type InputProvider = (
|
|
488
|
+
export type InputProvider = (ask: AskYield) => Promise<any>;
|
|
116
489
|
/**
|
|
117
|
-
* Handler for
|
|
490
|
+
* Handler for emit yields (output).
|
|
491
|
+
*
|
|
492
|
+
* Runtimes implement this to handle output:
|
|
493
|
+
* - CLI: console.log, progress bar
|
|
494
|
+
* - WebSocket: push message to client
|
|
495
|
+
* - REST: collect for response or send via SSE
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* const cliOutputHandler: OutputHandler = (emit) => {
|
|
499
|
+
* if (emit.emit === 'status') console.log(emit.message);
|
|
500
|
+
* if (emit.emit === 'progress') updateProgressBar(emit.value);
|
|
501
|
+
* };
|
|
118
502
|
*/
|
|
119
|
-
export type OutputHandler = (
|
|
503
|
+
export type OutputHandler = (emit: EmitYield) => void | Promise<void>;
|
|
120
504
|
/**
|
|
121
505
|
* Configuration for generator execution
|
|
122
506
|
*/
|
|
123
507
|
export interface GeneratorExecutorConfig {
|
|
124
|
-
/**
|
|
508
|
+
/**
|
|
509
|
+
* Provides input values for ask yields.
|
|
510
|
+
* Required unless all asks are pre-provided.
|
|
511
|
+
*/
|
|
125
512
|
inputProvider: InputProvider;
|
|
126
|
-
/**
|
|
513
|
+
/**
|
|
514
|
+
* Handles emit yields (optional).
|
|
515
|
+
* If not provided, emits are silently ignored.
|
|
516
|
+
*/
|
|
127
517
|
outputHandler?: OutputHandler;
|
|
128
|
-
/**
|
|
518
|
+
/**
|
|
519
|
+
* Pre-provided inputs keyed by ask id.
|
|
520
|
+
* Used by REST APIs to pass all inputs upfront.
|
|
521
|
+
*
|
|
522
|
+
* @example
|
|
523
|
+
* // If photon yields { ask: 'text', id: 'name', message: '...' }
|
|
524
|
+
* // and preProvidedInputs = { name: 'John' }
|
|
525
|
+
* // The generator receives 'John' without calling inputProvider
|
|
526
|
+
*/
|
|
129
527
|
preProvidedInputs?: Record<string, any>;
|
|
130
|
-
/**
|
|
528
|
+
/**
|
|
529
|
+
* Timeout for waiting on input (ms).
|
|
530
|
+
* @default 300000 (5 minutes)
|
|
531
|
+
*/
|
|
131
532
|
inputTimeout?: number;
|
|
132
533
|
}
|
|
133
534
|
/**
|
|
134
|
-
* Execute a generator-based tool
|
|
535
|
+
* Execute a generator-based photon tool to completion.
|
|
536
|
+
*
|
|
537
|
+
* Handles the yield/resume loop:
|
|
538
|
+
* 1. Run generator until it yields
|
|
539
|
+
* 2. If ask yield: get input from provider (or pre-provided), resume with value
|
|
540
|
+
* 3. If emit yield: call output handler, resume without value
|
|
541
|
+
* 4. Repeat until generator returns
|
|
135
542
|
*
|
|
136
543
|
* @param generator - The async generator to execute
|
|
137
544
|
* @param config - Configuration for handling yields
|
|
138
545
|
* @returns The final return value of the generator
|
|
139
546
|
*
|
|
140
547
|
* @example
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* if ('
|
|
145
|
-
* if ('confirm' in y) return await confirm(y.confirm);
|
|
548
|
+
* const result = await executeGenerator(photon.connect({ ip: '192.168.1.1' }), {
|
|
549
|
+
* inputProvider: async (ask) => {
|
|
550
|
+
* if (ask.ask === 'text') return await readline(ask.message);
|
|
551
|
+
* if (ask.ask === 'confirm') return await confirm(ask.message);
|
|
146
552
|
* },
|
|
147
|
-
* outputHandler: (
|
|
148
|
-
* if ('progress'
|
|
553
|
+
* outputHandler: (emit) => {
|
|
554
|
+
* if (emit.emit === 'progress') console.log(`${emit.value * 100}%`);
|
|
149
555
|
* }
|
|
150
556
|
* });
|
|
151
|
-
* ```
|
|
152
557
|
*/
|
|
153
558
|
export declare function executeGenerator<T>(generator: AsyncGenerator<PhotonYield, T, any>, config: GeneratorExecutorConfig): Promise<T>;
|
|
154
559
|
/**
|
|
155
|
-
*
|
|
156
|
-
|
|
157
|
-
export declare function isAsyncGeneratorFunction(fn: any): fn is (...args: any[]) => AsyncGenerator;
|
|
158
|
-
/**
|
|
159
|
-
* Check if a value is an async generator (already invoked)
|
|
560
|
+
* Information about an ask yield extracted from a generator.
|
|
561
|
+
* Used to generate REST API schemas (optional parameters).
|
|
160
562
|
*/
|
|
161
|
-
export
|
|
162
|
-
/**
|
|
163
|
-
* Information about a yield point extracted from a generator
|
|
164
|
-
*/
|
|
165
|
-
export interface ExtractedYield {
|
|
563
|
+
export interface ExtractedAsk {
|
|
166
564
|
id: string;
|
|
167
|
-
type: '
|
|
168
|
-
|
|
565
|
+
type: AskYield['ask'];
|
|
566
|
+
message: string;
|
|
567
|
+
required?: boolean;
|
|
568
|
+
default?: any;
|
|
169
569
|
options?: Array<string | {
|
|
170
570
|
value: string;
|
|
171
571
|
label: string;
|
|
172
572
|
}>;
|
|
173
|
-
default?: string;
|
|
174
|
-
required?: boolean;
|
|
175
573
|
pattern?: string;
|
|
176
|
-
|
|
177
|
-
|
|
574
|
+
min?: number;
|
|
575
|
+
max?: number;
|
|
178
576
|
}
|
|
179
577
|
/**
|
|
180
|
-
* Extract yield information by running generator with mock provider
|
|
181
|
-
* This is used for REST API schema generation
|
|
578
|
+
* Extract ask yield information by running generator with mock provider.
|
|
182
579
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
*
|
|
189
|
-
*
|
|
580
|
+
* This is used for REST API schema generation - each ask becomes
|
|
581
|
+
* an optional request parameter.
|
|
582
|
+
*
|
|
583
|
+
* Note: Only extracts asks reachable with default/empty inputs.
|
|
584
|
+
* Conditional asks may not be discovered.
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* const asks = await extractAsks(Photon.prototype.connect, { ip: '' });
|
|
588
|
+
* // Returns: [{ id: 'pairing_code', type: 'text', message: '...' }]
|
|
589
|
+
* // These become optional query/body params in REST API
|
|
190
590
|
*/
|
|
191
|
-
export declare function
|
|
591
|
+
export declare function extractAsks(generatorFn: (...args: any[]) => AsyncGenerator<PhotonYield, any, any>, mockParams?: any): Promise<ExtractedAsk[]>;
|
|
192
592
|
/**
|
|
193
|
-
* Error thrown when input is
|
|
194
|
-
*
|
|
593
|
+
* Error thrown when input is required but not available.
|
|
594
|
+
*
|
|
595
|
+
* REST APIs can catch this to return a continuation response.
|
|
596
|
+
*
|
|
597
|
+
* @example
|
|
598
|
+
* try {
|
|
599
|
+
* await executeGenerator(gen, { inputProvider: createPrefilledProvider({}) });
|
|
600
|
+
* } catch (e) {
|
|
601
|
+
* if (e instanceof NeedsInputError) {
|
|
602
|
+
* return {
|
|
603
|
+
* status: 'awaiting_input',
|
|
604
|
+
* ask: e.ask,
|
|
605
|
+
* continuation_id: saveContinuation(gen)
|
|
606
|
+
* };
|
|
607
|
+
* }
|
|
608
|
+
* }
|
|
195
609
|
*/
|
|
196
610
|
export declare class NeedsInputError extends Error {
|
|
197
|
-
readonly
|
|
198
|
-
constructor(
|
|
611
|
+
readonly ask: AskYield;
|
|
612
|
+
constructor(ask: AskYield);
|
|
199
613
|
}
|
|
200
614
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
615
|
+
* Create an input provider from pre-provided values.
|
|
616
|
+
* Throws NeedsInputError if a required value is missing.
|
|
617
|
+
*
|
|
618
|
+
* Use for REST APIs where all inputs are provided upfront.
|
|
619
|
+
*
|
|
620
|
+
* @example
|
|
621
|
+
* const provider = createPrefilledProvider({
|
|
622
|
+
* name: 'John',
|
|
623
|
+
* confirmed: true
|
|
624
|
+
* });
|
|
625
|
+
*/
|
|
626
|
+
export declare function createPrefilledProvider(inputs: Record<string, any>): InputProvider;
|
|
627
|
+
/**
|
|
628
|
+
* Wrap a regular async function to behave like a generator.
|
|
629
|
+
* Useful for uniform handling in runtimes.
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* const gen = wrapAsGenerator(() => photon.simpleMethod(params));
|
|
633
|
+
* const result = await executeGenerator(gen, config);
|
|
203
634
|
*/
|
|
204
635
|
export declare function wrapAsGenerator<T>(asyncFn: () => Promise<T>): AsyncGenerator<never, T, unknown>;
|
|
636
|
+
/**
|
|
637
|
+
* @deprecated Use AskYield instead
|
|
638
|
+
*/
|
|
639
|
+
export type PromptYield = AskText | AskPassword;
|
|
640
|
+
/**
|
|
641
|
+
* @deprecated Use AskConfirm instead
|
|
642
|
+
*/
|
|
643
|
+
export type ConfirmYield = AskConfirm;
|
|
644
|
+
/**
|
|
645
|
+
* @deprecated Use AskSelect instead
|
|
646
|
+
*/
|
|
647
|
+
export type SelectYield = AskSelect;
|
|
648
|
+
/**
|
|
649
|
+
* @deprecated Use EmitProgress instead
|
|
650
|
+
*/
|
|
651
|
+
export type ProgressYield = EmitProgress;
|
|
652
|
+
/**
|
|
653
|
+
* @deprecated Use EmitStream instead
|
|
654
|
+
*/
|
|
655
|
+
export type StreamYield = EmitStream;
|
|
656
|
+
/**
|
|
657
|
+
* @deprecated Use EmitLog instead
|
|
658
|
+
*/
|
|
659
|
+
export type LogYield = EmitLog;
|
|
660
|
+
/**
|
|
661
|
+
* @deprecated Use isAskYield instead
|
|
662
|
+
*/
|
|
663
|
+
export declare const isInputYield: typeof isAskYield;
|
|
664
|
+
/**
|
|
665
|
+
* @deprecated Use isEmitYield instead
|
|
666
|
+
*/
|
|
667
|
+
export declare function isProgressYield(y: PhotonYield): y is EmitProgress;
|
|
668
|
+
/**
|
|
669
|
+
* @deprecated Use isEmitYield instead
|
|
670
|
+
*/
|
|
671
|
+
export declare function isStreamYield(y: PhotonYield): y is EmitStream;
|
|
672
|
+
/**
|
|
673
|
+
* @deprecated Use isEmitYield instead
|
|
674
|
+
*/
|
|
675
|
+
export declare function isLogYield(y: PhotonYield): y is EmitLog;
|
|
676
|
+
/**
|
|
677
|
+
* @deprecated Use extractAsks instead
|
|
678
|
+
*/
|
|
679
|
+
export declare const extractYields: typeof extractAsks;
|
|
680
|
+
/**
|
|
681
|
+
* @deprecated Use ExtractedAsk instead
|
|
682
|
+
*/
|
|
683
|
+
export type ExtractedYield = ExtractedAsk;
|
|
684
|
+
export {};
|
|
205
685
|
//# sourceMappingURL=generator.d.ts.map
|