@positronic/core 0.0.1 → 0.0.3

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 (79) hide show
  1. package/CLAUDE.md +141 -0
  2. package/dist/src/adapters/types.js +1 -16
  3. package/dist/src/clients/types.js +4 -1
  4. package/dist/src/dsl/brain-runner.js +487 -0
  5. package/dist/src/dsl/brain-runner.test.js +733 -0
  6. package/dist/src/dsl/brain.js +1128 -0
  7. package/dist/src/dsl/brain.test.js +4225 -0
  8. package/dist/src/dsl/constants.js +6 -6
  9. package/dist/src/dsl/json-patch.js +37 -9
  10. package/dist/src/index.js +11 -10
  11. package/dist/src/resources/resources.js +371 -0
  12. package/dist/src/test-utils.js +474 -0
  13. package/dist/src/testing.js +3 -0
  14. package/dist/types/adapters/types.d.ts +3 -8
  15. package/dist/types/adapters/types.d.ts.map +1 -1
  16. package/dist/types/clients/types.d.ts +46 -6
  17. package/dist/types/clients/types.d.ts.map +1 -1
  18. package/dist/types/dsl/brain-runner.d.ts +24 -0
  19. package/dist/types/dsl/brain-runner.d.ts.map +1 -0
  20. package/dist/types/dsl/brain.d.ts +136 -0
  21. package/dist/types/dsl/brain.d.ts.map +1 -0
  22. package/dist/types/dsl/constants.d.ts +5 -5
  23. package/dist/types/dsl/constants.d.ts.map +1 -1
  24. package/dist/types/dsl/json-patch.d.ts +2 -1
  25. package/dist/types/dsl/json-patch.d.ts.map +1 -1
  26. package/dist/types/index.d.ts +13 -11
  27. package/dist/types/index.d.ts.map +1 -1
  28. package/dist/types/resources/resource-loader.d.ts +6 -0
  29. package/dist/types/resources/resource-loader.d.ts.map +1 -0
  30. package/dist/types/resources/resources.d.ts +23 -0
  31. package/dist/types/resources/resources.d.ts.map +1 -0
  32. package/dist/types/test-utils.d.ts +94 -0
  33. package/dist/types/test-utils.d.ts.map +1 -0
  34. package/dist/types/testing.d.ts +2 -0
  35. package/dist/types/testing.d.ts.map +1 -0
  36. package/docs/core-testing-guide.md +289 -0
  37. package/package.json +26 -7
  38. package/src/adapters/types.ts +3 -22
  39. package/src/clients/types.ts +50 -10
  40. package/src/dsl/brain-runner.test.ts +384 -0
  41. package/src/dsl/brain-runner.ts +111 -0
  42. package/src/dsl/brain.test.ts +1981 -0
  43. package/src/dsl/brain.ts +740 -0
  44. package/src/dsl/constants.ts +6 -6
  45. package/src/dsl/json-patch.ts +24 -9
  46. package/src/dsl/types.ts +1 -1
  47. package/src/index.ts +30 -16
  48. package/src/resources/resource-loader.ts +8 -0
  49. package/src/resources/resources.ts +267 -0
  50. package/src/test-utils.ts +254 -0
  51. package/test/resources.test.ts +248 -0
  52. package/tsconfig.json +2 -2
  53. package/.swcrc +0 -31
  54. package/dist/src/dsl/extensions.js +0 -19
  55. package/dist/src/dsl/workflow-runner.js +0 -93
  56. package/dist/src/dsl/workflow.js +0 -308
  57. package/dist/src/file-stores/local-file-store.js +0 -12
  58. package/dist/src/utils/temp-files.js +0 -27
  59. package/dist/types/dsl/extensions.d.ts +0 -18
  60. package/dist/types/dsl/extensions.d.ts.map +0 -1
  61. package/dist/types/dsl/workflow-runner.d.ts +0 -28
  62. package/dist/types/dsl/workflow-runner.d.ts.map +0 -1
  63. package/dist/types/dsl/workflow.d.ts +0 -118
  64. package/dist/types/dsl/workflow.d.ts.map +0 -1
  65. package/dist/types/file-stores/local-file-store.d.ts +0 -7
  66. package/dist/types/file-stores/local-file-store.d.ts.map +0 -1
  67. package/dist/types/file-stores/types.d.ts +0 -4
  68. package/dist/types/file-stores/types.d.ts.map +0 -1
  69. package/dist/types/utils/temp-files.d.ts +0 -12
  70. package/dist/types/utils/temp-files.d.ts.map +0 -1
  71. package/src/dsl/extensions.ts +0 -58
  72. package/src/dsl/workflow-runner.test.ts +0 -203
  73. package/src/dsl/workflow-runner.ts +0 -146
  74. package/src/dsl/workflow.test.ts +0 -1435
  75. package/src/dsl/workflow.ts +0 -554
  76. package/src/file-stores/local-file-store.ts +0 -11
  77. package/src/file-stores/types.ts +0 -3
  78. package/src/utils/temp-files.ts +0 -46
  79. /package/dist/src/{file-stores/types.js → resources/resource-loader.js} +0 -0
@@ -1,554 +0,0 @@
1
- import { z } from "zod";
2
- import { v4 as uuidv4 } from 'uuid';
3
- import type { PromptClient } from "../clients/types";
4
- import type { State, JsonPatch } from "./types";
5
- import { STATUS, WORKFLOW_EVENTS } from './constants';
6
- import { createPatch, applyPatches } from './json-patch';
7
- import type { FileStore } from "../file-stores/types";
8
-
9
- export type SerializedError = {
10
- name: string;
11
- message: string;
12
- stack?: string;
13
- }
14
-
15
- // New Event Type System
16
- // Base event interface with only type and options
17
- interface BaseEvent<TOptions extends object = {}> {
18
- type: typeof WORKFLOW_EVENTS[keyof typeof WORKFLOW_EVENTS];
19
- options: TOptions;
20
- workflowRunId: string;
21
- }
22
-
23
- // 1. Workflow Events (all include workflow title/description)
24
- interface WorkflowBaseEvent<TOptions extends object = {}> extends BaseEvent<TOptions> {
25
- workflowTitle: string;
26
- workflowDescription?: string;
27
- }
28
-
29
- export interface WorkflowStartEvent<TOptions extends object = {}> extends WorkflowBaseEvent<TOptions> {
30
- type: typeof WORKFLOW_EVENTS.START | typeof WORKFLOW_EVENTS.RESTART;
31
- initialState: State;
32
- status: typeof STATUS.RUNNING;
33
- }
34
-
35
- export interface WorkflowCompleteEvent<TOptions extends object = {}> extends WorkflowBaseEvent<TOptions> {
36
- type: typeof WORKFLOW_EVENTS.COMPLETE;
37
- status: typeof STATUS.COMPLETE;
38
- }
39
-
40
- export interface WorkflowErrorEvent<TOptions extends object = {}> extends WorkflowBaseEvent<TOptions> {
41
- type: typeof WORKFLOW_EVENTS.ERROR;
42
- status: typeof STATUS.ERROR;
43
- error: SerializedError;
44
- }
45
-
46
- // 2. Step Status Event (just steps array and base event properties)
47
- export interface StepStatusEvent<TOptions extends object = {}> extends BaseEvent<TOptions> {
48
- type: typeof WORKFLOW_EVENTS.STEP_STATUS;
49
- steps: SerializedStep[];
50
- }
51
-
52
- // 3. Step Events (include step-specific properties)
53
- export interface StepStartedEvent<TOptions extends object = {}> extends BaseEvent<TOptions> {
54
- type: typeof WORKFLOW_EVENTS.STEP_START;
55
- status: typeof STATUS.RUNNING;
56
- stepTitle: string;
57
- stepId: string;
58
- }
59
-
60
- export interface StepCompletedEvent<TOptions extends object = {}> extends BaseEvent<TOptions> {
61
- type: typeof WORKFLOW_EVENTS.STEP_COMPLETE;
62
- status: typeof STATUS.RUNNING;
63
- stepTitle: string;
64
- stepId: string;
65
- patch: JsonPatch;
66
- }
67
-
68
- // Union type of all possible events
69
- export type WorkflowEvent<TOptions extends object = {}> =
70
- | WorkflowStartEvent<TOptions>
71
- | WorkflowCompleteEvent<TOptions>
72
- | WorkflowErrorEvent<TOptions>
73
- | StepStatusEvent<TOptions>
74
- | StepStartedEvent<TOptions>
75
- | StepCompletedEvent<TOptions>;
76
-
77
- export interface SerializedStep {
78
- title: string;
79
- status: typeof STATUS[keyof typeof STATUS];
80
- id: string;
81
- patch?: JsonPatch;
82
- }
83
-
84
- type StepBlock<TStateIn, TStateOut, TOptions extends object = {}> = {
85
- type: 'step';
86
- title: string;
87
- action: (params: {
88
- state: TStateIn;
89
- options: TOptions;
90
- client: PromptClient;
91
- fileStore: FileStore;
92
- }) => TStateOut | Promise<TStateOut>;
93
- };
94
-
95
- type WorkflowBlock<
96
- TOuterState,
97
- TInnerState extends State,
98
- TNewState,
99
- TOptions extends object = {}
100
- > = {
101
- type: 'workflow';
102
- title: string;
103
- innerWorkflow: Workflow<TOptions, TInnerState>;
104
- initialState: State | ((outerState: TOuterState) => State);
105
- action: (outerState: TOuterState, innerState: TInnerState) => TNewState;
106
- };
107
-
108
- type Block<TStateIn, TStateOut, TOptions extends object = {}> =
109
- | StepBlock<TStateIn, TStateOut, TOptions>
110
- | WorkflowBlock<TStateIn, any, TStateOut, TOptions>;
111
-
112
- interface BaseRunParams<TOptions extends object = {}> {
113
- fileStore: FileStore;
114
- client: PromptClient;
115
- options?: TOptions;
116
- }
117
-
118
- export interface InitialRunParams<TOptions extends object = {}> extends BaseRunParams<TOptions> {
119
- initialState?: State;
120
- initialCompletedSteps?: never;
121
- workflowRunId?: never;
122
- }
123
-
124
- export interface RerunParams<TOptions extends object = {}> extends BaseRunParams<TOptions> {
125
- initialState: State;
126
- initialCompletedSteps: SerializedStep[];
127
- workflowRunId: string;
128
- }
129
-
130
- export class Workflow<
131
- TOptions extends object = {},
132
- TState extends State = {}
133
- > {
134
- private blocks: Block<any, any, TOptions>[] = [];
135
- public type: 'workflow' = 'workflow';
136
-
137
- constructor(
138
- public readonly title: string,
139
- private description?: string
140
- ) {}
141
-
142
- step<TNewState extends State>(
143
- title: string,
144
- action: (params: {
145
- state: TState;
146
- options: TOptions;
147
- client: PromptClient;
148
- fileStore: FileStore;
149
- }) => TNewState | Promise<TNewState>
150
- ) {
151
- const stepBlock: StepBlock<TState, TNewState, TOptions> = {
152
- type: 'step',
153
- title,
154
- action
155
- };
156
- this.blocks.push(stepBlock);
157
- return this.nextWorkflow<TNewState>();
158
- }
159
-
160
- workflow<
161
- TInnerState extends State,
162
- TNewState extends State
163
- >(
164
- title: string,
165
- innerWorkflow: Workflow<TOptions, TInnerState>,
166
- action: (params: { state: TState; workflowState: TInnerState }) => TNewState,
167
- initialState?: State | ((state: TState) => State)
168
- ) {
169
- const nestedBlock: WorkflowBlock<
170
- TState,
171
- TInnerState,
172
- TNewState,
173
- TOptions
174
- > = {
175
- type: 'workflow',
176
- title,
177
- innerWorkflow,
178
- initialState: initialState || (() => ({} as State)),
179
- action: (outerState, innerState) => action({ state: outerState, workflowState: innerState})
180
- };
181
- this.blocks.push(nestedBlock);
182
- return this.nextWorkflow<TNewState>();
183
- }
184
-
185
- // TResponseKey:
186
- // The response key must be a string literal, so if defining a response model
187
- // a consumer of this workflow must use "as const" to ensure the key is a string literal
188
- // this type makes sure that the will get a ts error if they don't.
189
- prompt<
190
- TResponseKey extends string & { readonly brand?: unique symbol },
191
- TSchema extends z.ZodObject<any>,
192
- TNewState extends State = TState & { [K in TResponseKey]: z.infer<TSchema> }
193
- >(
194
- title: string,
195
- config: {
196
- template: (state: TState) => string;
197
- responseModel: {
198
- schema: TSchema;
199
- name: TResponseKey & (string extends TResponseKey ? never : unknown);
200
- };
201
- client?: PromptClient;
202
- },
203
- reduce?: (params: {
204
- state: TState,
205
- response: z.infer<TSchema>,
206
- options: TOptions,
207
- prompt: string
208
- }) => TNewState | Promise<TNewState>,
209
- ) {
210
- const promptBlock: StepBlock<
211
- TState,
212
- TNewState,
213
- TOptions
214
- > = {
215
- type: 'step',
216
- title,
217
- action: async ({ state, client: runClient, options }) => {
218
- const { template, responseModel, client: stepClient } = config;
219
- const client = stepClient ?? runClient;
220
- const promptString = template(state);
221
- const response = await client.execute(promptString, responseModel);
222
- const stateWithResponse = {
223
- ...state,
224
- [config.responseModel.name]: response
225
- };
226
-
227
- return reduce
228
- ? reduce({ state, response, options, prompt: promptString })
229
- : stateWithResponse as unknown as TNewState;
230
- }
231
- };
232
- this.blocks.push(promptBlock);
233
- return this.nextWorkflow<TNewState>();
234
- }
235
-
236
- // Overload signatures
237
- run(params: InitialRunParams<TOptions>): AsyncGenerator<WorkflowEvent<TOptions>>;
238
- run(params: RerunParams<TOptions>): AsyncGenerator<WorkflowEvent<TOptions>>;
239
-
240
- // Implementation signature
241
- async *run(params: InitialRunParams<TOptions> | RerunParams<TOptions>): AsyncGenerator<WorkflowEvent<TOptions>> {
242
- const { title, description, blocks } = this;
243
- const stream = new WorkflowEventStream({
244
- title,
245
- description,
246
- blocks,
247
- ...params
248
- });
249
-
250
- yield* stream.next();
251
- }
252
-
253
- private withBlocks(blocks: Block<any, any, TOptions>[]): this {
254
- this.blocks = blocks;
255
- return this;
256
- }
257
-
258
- private nextWorkflow<TNewState extends State>(): Workflow<TOptions, TNewState> {
259
- return new Workflow<TOptions, TNewState>(
260
- this.title,
261
- this.description
262
- ).withBlocks(this.blocks);
263
- }
264
- }
265
-
266
- class Step {
267
- public id: string;
268
- private patch?: JsonPatch | string;
269
- private status: typeof STATUS[keyof typeof STATUS] = STATUS.PENDING;
270
-
271
- constructor(
272
- public block: Block<any, any, any>,
273
- id?: string
274
- ) {
275
- this.id = id || uuidv4();
276
- }
277
-
278
- withPatch(patch: JsonPatch | undefined) {
279
- this.patch = patch;
280
- return this;
281
- }
282
-
283
- withStatus(status: typeof STATUS[keyof typeof STATUS]) {
284
- this.status = status;
285
- return this;
286
- }
287
-
288
- get serialized(): SerializedStep {
289
- return {
290
- id: this.id,
291
- title: this.block.title,
292
- status: this.status,
293
- patch: typeof this.patch === 'string' ? JSON.parse(this.patch) : this.patch
294
- };
295
- }
296
- }
297
-
298
- class WorkflowEventStream<TOptions extends object = {}, TState extends State = {}> {
299
- private currentState: TState;
300
- private steps: Step[];
301
- private currentStepIndex: number = 0;
302
- private initialState: TState;
303
- private workflowRunId: string;
304
- private title: string;
305
- private description?: string;
306
- private fileStore: FileStore;
307
- private client: PromptClient;
308
- private options: TOptions;
309
-
310
- constructor(params: (InitialRunParams<TOptions> | RerunParams<TOptions>) & {
311
- title: string;
312
- description?: string;
313
- blocks: Block<any, any, TOptions>[];
314
- }) {
315
- const {
316
- initialState = {} as TState,
317
- initialCompletedSteps,
318
- blocks,
319
- title,
320
- description,
321
- workflowRunId: providedWorkflowRunId,
322
- options = {} as TOptions,
323
- fileStore,
324
- client
325
- } = params;
326
-
327
- this.initialState = initialState as TState;
328
- this.title = title;
329
- this.description = description;
330
- this.fileStore = fileStore;
331
- this.client = client;
332
- this.options = options;
333
-
334
- // Initialize steps array with UUIDs and pending status
335
- this.steps = blocks.map((block, index) => {
336
- const completedStep = initialCompletedSteps?.[index];
337
- if (completedStep) {
338
- return new Step(block, completedStep.id)
339
- .withStatus(completedStep.status)
340
- .withPatch(completedStep.patch);
341
- }
342
- return new Step(block);
343
- });
344
-
345
- this.currentState = clone(this.initialState);
346
-
347
- for (const step of this.steps) {
348
- if (step.serialized.status === STATUS.COMPLETE && step.serialized.patch) {
349
- this.currentState = applyPatches(this.currentState, [step.serialized.patch]) as TState;
350
- }
351
- }
352
-
353
- this.workflowRunId = providedWorkflowRunId ?? uuidv4();
354
- }
355
-
356
- async *next(): AsyncGenerator<WorkflowEvent<TOptions>> {
357
- const {
358
- steps,
359
- title: workflowTitle,
360
- description: workflowDescription,
361
- currentState,
362
- options,
363
- workflowRunId
364
- } = this;
365
-
366
- try {
367
- const hasCompletedSteps = steps.some(step => step.serialized.status !== STATUS.PENDING);
368
- yield {
369
- type: hasCompletedSteps ? WORKFLOW_EVENTS.RESTART : WORKFLOW_EVENTS.START,
370
- status: STATUS.RUNNING,
371
- workflowTitle,
372
- workflowDescription,
373
- initialState: currentState,
374
- options,
375
- workflowRunId
376
- };
377
-
378
- // Emit initial step status after workflow starts
379
- yield {
380
- type: WORKFLOW_EVENTS.STEP_STATUS,
381
- steps: steps.map(step => step.serialized),
382
- options,
383
- workflowRunId
384
- };
385
-
386
- // Process each step
387
- while (this.currentStepIndex < steps.length) {
388
- const step = steps[this.currentStepIndex];
389
-
390
- // Skip completed steps
391
- if (step.serialized.status === STATUS.COMPLETE) {
392
- this.currentStepIndex++;
393
- continue;
394
- }
395
- // Step start event
396
- yield {
397
- type: WORKFLOW_EVENTS.STEP_START,
398
- status: STATUS.RUNNING,
399
- stepTitle: step.block.title,
400
- stepId: step.id,
401
- options,
402
- workflowRunId
403
- };
404
-
405
- // Execute step and yield the STEP_COMPLETE event and
406
- // all events from inner workflows if any
407
- yield* this.executeStep(step);
408
-
409
- // Step Status Event
410
- yield {
411
- type: WORKFLOW_EVENTS.STEP_STATUS,
412
- steps: steps.map(step => step.serialized),
413
- options,
414
- workflowRunId
415
- };
416
-
417
- this.currentStepIndex++;
418
- }
419
-
420
- yield {
421
- type: WORKFLOW_EVENTS.COMPLETE,
422
- status: STATUS.COMPLETE,
423
- workflowTitle,
424
- workflowDescription,
425
- workflowRunId,
426
- options
427
- };
428
-
429
- } catch (err: any) {
430
- const error = err as Error;
431
- const currentStep = steps[this.currentStepIndex];
432
- currentStep?.withStatus(STATUS.ERROR);
433
-
434
- yield {
435
- type: WORKFLOW_EVENTS.ERROR,
436
- status: STATUS.ERROR,
437
- workflowTitle,
438
- workflowDescription,
439
- workflowRunId,
440
- error: {
441
- name: error.name,
442
- message: error.message,
443
- stack: error.stack
444
- },
445
- options,
446
- };
447
-
448
- // Step Status Event
449
- yield {
450
- type: WORKFLOW_EVENTS.STEP_STATUS,
451
- steps: steps.map(step => step.serialized),
452
- options,
453
- workflowRunId
454
- };
455
-
456
- throw error;
457
- }
458
- }
459
-
460
- private async *executeStep(step: Step): AsyncGenerator<WorkflowEvent<TOptions>> {
461
- const block = step.block;
462
-
463
- if (block.type === 'workflow') {
464
- const initialState = typeof block.initialState === 'function'
465
- ? block.initialState(this.currentState)
466
- : block.initialState;
467
-
468
- // Run inner workflow and yield all its events
469
- let patches: JsonPatch[] = [];
470
- const innerRun = block.innerWorkflow.run({
471
- fileStore: this.fileStore,
472
- client: this.client,
473
- initialState,
474
- options: this.options ?? {} as TOptions,
475
- });
476
-
477
- for await (const event of innerRun) {
478
- yield event; // Forward all inner workflow events
479
- if (event.type === WORKFLOW_EVENTS.STEP_COMPLETE) {
480
- patches.push(event.patch);
481
- }
482
- }
483
-
484
- // Apply collected patches to get final inner state
485
- const innerState = applyPatches(initialState, patches);
486
-
487
- // Get previous state before action
488
- const prevState = this.currentState;
489
-
490
- // Update state with inner workflow results
491
- this.currentState = await block.action(this.currentState, innerState);
492
- yield* this.completeStep(step, prevState);
493
- } else {
494
- // Get previous state before action
495
- const prevState = this.currentState;
496
-
497
- // Execute regular step
498
- this.currentState = await block.action({
499
- state: this.currentState,
500
- options: this.options ?? {} as TOptions,
501
- client: this.client,
502
- fileStore: this.fileStore,
503
- });
504
- yield* this.completeStep(step, prevState);
505
- }
506
- }
507
-
508
- private *completeStep(step: Step, prevState: TState): Generator<WorkflowEvent<TOptions>> {
509
- step.withStatus(STATUS.COMPLETE);
510
-
511
- // Create patch for the state change
512
- const patch = createPatch(prevState, this.currentState);
513
- step.withPatch(patch);
514
-
515
- yield {
516
- type: WORKFLOW_EVENTS.STEP_COMPLETE,
517
- status: STATUS.RUNNING,
518
- stepTitle: step.block.title,
519
- stepId: step.id,
520
- patch,
521
- options: this.options ?? {} as TOptions,
522
- workflowRunId: this.workflowRunId
523
- };
524
- }
525
- }
526
-
527
- let workflowNamesAreUnique = true;
528
- export function disableWorkflowNameUniqueness() {
529
- workflowNamesAreUnique = false;
530
- }
531
-
532
- export function enableWorkflowNameUniqueness() {
533
- workflowNamesAreUnique = true;
534
- }
535
-
536
- const workflowNames = new Set<string>();
537
- export function workflow<
538
- TOptions extends object = {},
539
- TState extends State = {}
540
- >(
541
- workflowConfig: string | { title: string; description?: string }
542
- ) {
543
- const title = typeof workflowConfig === 'string' ? workflowConfig : workflowConfig.title;
544
- const description = typeof workflowConfig === 'string' ? undefined : workflowConfig.description;
545
- if (workflowNamesAreUnique && workflowNames.has(title)) {
546
- throw new Error(`Workflow with name "${title}" already exists. Workflow names must be unique.`);
547
- }
548
- if (workflowNamesAreUnique) {
549
- workflowNames.add(title);
550
- }
551
- return new Workflow<TOptions, TState>(title, description);
552
- }
553
-
554
- const clone = <T>(value: T): T => structuredClone(value);
@@ -1,11 +0,0 @@
1
- import { join } from 'path';
2
- import * as fs from 'fs/promises';
3
- import type { FileStore } from './types';
4
-
5
- export class LocalFileStore implements FileStore {
6
- constructor(private baseDir: string) {}
7
- async readFile(path: string): Promise<string> {
8
- const filePath = join(this.baseDir, path);
9
- return fs.readFile(filePath, 'utf-8');
10
- }
11
- }
@@ -1,3 +0,0 @@
1
- export interface FileStore {
2
- readFile(path: string): Promise<string>;
3
- }
@@ -1,46 +0,0 @@
1
- import * as path from 'path';
2
- import * as os from 'os';
3
- import * as fs from 'fs/promises';
4
- import { FileStore } from '../file-stores/types';
5
- import { exec } from 'child_process';
6
- import { promisify } from 'util';
7
-
8
- const execAsync = promisify(exec);
9
-
10
- /**
11
- * Writes content to a temporary file and opens it in cursor
12
- */
13
- export async function writeAndOpenTemp(
14
- content: string,
15
- prefix: string,
16
- extension: string = 'txt'
17
- ): Promise<string> {
18
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'positronic-'));
19
- const filename = `${prefix}-${Date.now()}.${extension}`;
20
- const filepath = path.join(tempDir, filename);
21
-
22
- await fs.writeFile(filepath, content, 'utf8');
23
-
24
- // Open in cursor using the cursor command
25
- await execAsync(`cursor ${filepath}`);
26
-
27
- return filepath;
28
- }
29
-
30
- /**
31
- * Writes both prompt and response to temp files and opens them in cursor
32
- */
33
- export async function writePromptAndResponse(
34
- prompt: string,
35
- response: unknown,
36
- prefix: string = 'debug'
37
- ): Promise<{ promptPath: string, responsePath: string }> {
38
- const promptPath = await writeAndOpenTemp(prompt, `${prefix}-prompt`, 'md');
39
- const responsePath = await writeAndOpenTemp(
40
- typeof response === 'string' ? response : JSON.stringify(response, null, 2),
41
- `${prefix}-response`,
42
- 'tsx'
43
- );
44
-
45
- return { promptPath, responsePath };
46
- }