libretto 0.2.5 → 0.2.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/README.md
CHANGED
|
@@ -9,17 +9,21 @@ pnpm add libretto playwright zod
|
|
|
9
9
|
npx libretto init
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
> **pnpm users:**
|
|
13
|
-
>
|
|
12
|
+
> **pnpm users:** if your workspace uses `onlyBuiltDependencies`, add both
|
|
13
|
+
> `libretto` and `playwright` to allow their postinstall scripts to run
|
|
14
|
+
> (libretto's postinstall copies skill files and installs Playwright Chromium):
|
|
14
15
|
>
|
|
15
16
|
> ```jsonc
|
|
16
17
|
> // package.json
|
|
17
18
|
> {
|
|
18
19
|
> "pnpm": {
|
|
19
|
-
> "onlyBuiltDependencies": ["playwright"]
|
|
20
|
+
> "onlyBuiltDependencies": ["libretto", "playwright"]
|
|
20
21
|
> }
|
|
21
22
|
> }
|
|
22
23
|
> ```
|
|
24
|
+
>
|
|
25
|
+
> If the postinstall was skipped (e.g., `libretto` wasn't in the allowlist),
|
|
26
|
+
> run `npx libretto init` manually after install to complete setup.
|
|
23
27
|
|
|
24
28
|
## Quick Start
|
|
25
29
|
|
|
@@ -119,6 +123,33 @@ Run `npx libretto help` for the full list.
|
|
|
119
123
|
| `libretto/run` | `launchBrowser` |
|
|
120
124
|
| `libretto/state` | Session state serialization and parsing |
|
|
121
125
|
|
|
126
|
+
## Using Recovery Helpers
|
|
127
|
+
|
|
128
|
+
The recovery module (`libretto/recovery`) provides `detectSubmissionError` and
|
|
129
|
+
`executeRecoveryAgent` for handling form submission errors. Both accept an
|
|
130
|
+
`LLMClient` — create one with `createLLMClientFromModel` and pass it directly:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { detectSubmissionError, executeRecoveryAgent } from "libretto/recovery";
|
|
134
|
+
import { createLLMClientFromModel } from "libretto/llm";
|
|
135
|
+
import { openai } from "@ai-sdk/openai";
|
|
136
|
+
|
|
137
|
+
const llmClient = createLLMClientFromModel(openai("gpt-4o"));
|
|
138
|
+
|
|
139
|
+
// Detect if a submission produced an error
|
|
140
|
+
const error = await detectSubmissionError(
|
|
141
|
+
page, submissionError, "eligibility check failed", llmClient, knownErrors, logger,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Or run the full recovery agent to retry with corrections
|
|
145
|
+
const result = await executeRecoveryAgent(
|
|
146
|
+
page, error, llmClient, recoveryOptions, logger,
|
|
147
|
+
);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
No need to write custom wrappers — `createLLMClientFromModel` bridges any
|
|
151
|
+
Vercel AI SDK provider into the `LLMClient` interface that recovery helpers expect.
|
|
152
|
+
|
|
122
153
|
## Links
|
|
123
154
|
|
|
124
155
|
- [GitHub](https://github.com/saffron-health/libretto)
|
|
@@ -168,8 +168,27 @@ class Logger {
|
|
|
168
168
|
options
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
|
-
info(event,
|
|
172
|
-
|
|
171
|
+
info(event, dataOrError, options) {
|
|
172
|
+
const data = dataOrError instanceof Error ? {
|
|
173
|
+
error: {
|
|
174
|
+
type: dataOrError.constructor.name,
|
|
175
|
+
message: dataOrError.message,
|
|
176
|
+
stack: dataOrError.stack || null
|
|
177
|
+
}
|
|
178
|
+
} : isObject(dataOrError) && dataOrError.error instanceof Error ? {
|
|
179
|
+
...dataOrError,
|
|
180
|
+
error: {
|
|
181
|
+
type: dataOrError.error.constructor.name,
|
|
182
|
+
message: dataOrError.error.message,
|
|
183
|
+
stack: dataOrError.error.stack || null
|
|
184
|
+
}
|
|
185
|
+
} : isObject(dataOrError) ? dataOrError : dataOrError !== void 0 ? { error: dataOrError } : void 0;
|
|
186
|
+
this.entry({
|
|
187
|
+
level: "info",
|
|
188
|
+
event,
|
|
189
|
+
data,
|
|
190
|
+
options
|
|
191
|
+
});
|
|
173
192
|
}
|
|
174
193
|
withScope(scope, context = {}) {
|
|
175
194
|
return new Logger([...this.scopes, scope], this.sinks, {
|
|
@@ -7,7 +7,7 @@ type LogOptions = {
|
|
|
7
7
|
* implement withScope, withContext, flush, etc.
|
|
8
8
|
*/
|
|
9
9
|
type MinimalLogger = {
|
|
10
|
-
info: (event: string, data?:
|
|
10
|
+
info: (event: string, data?: any) => void;
|
|
11
11
|
warn: (event: string, data?: any) => void;
|
|
12
12
|
error: (event: string, data?: any) => any;
|
|
13
13
|
};
|
|
@@ -26,7 +26,9 @@ type LoggerApi = {
|
|
|
26
26
|
warn: (event: string, data?: Error | ({
|
|
27
27
|
error: Error;
|
|
28
28
|
} & Record<string, any>) | unknown, options?: LogOptions) => void;
|
|
29
|
-
info: (event: string, data?:
|
|
29
|
+
info: (event: string, data?: Error | ({
|
|
30
|
+
error: Error;
|
|
31
|
+
} & Record<string, any>) | unknown, options?: LogOptions) => void;
|
|
30
32
|
/**
|
|
31
33
|
* Context passed in will be attached to all entries in this scope.
|
|
32
34
|
*/
|
|
@@ -71,7 +73,9 @@ declare class Logger implements LoggerApi {
|
|
|
71
73
|
warn(event: string, dataOrError?: Error | ({
|
|
72
74
|
error: Error;
|
|
73
75
|
} & Record<string, any>) | unknown, options?: LogOptions): void;
|
|
74
|
-
info(event: string,
|
|
76
|
+
info(event: string, dataOrError?: Error | ({
|
|
77
|
+
error: Error;
|
|
78
|
+
} & Record<string, any>) | unknown, options?: LogOptions): void;
|
|
75
79
|
withScope(scope: string, context?: Record<string, any>): LoggerApi;
|
|
76
80
|
withContext(context: Record<string, any>): LoggerApi;
|
|
77
81
|
withSink(sink: LoggerSink): Logger;
|
|
@@ -7,7 +7,7 @@ type LogOptions = {
|
|
|
7
7
|
* implement withScope, withContext, flush, etc.
|
|
8
8
|
*/
|
|
9
9
|
type MinimalLogger = {
|
|
10
|
-
info: (event: string, data?:
|
|
10
|
+
info: (event: string, data?: any) => void;
|
|
11
11
|
warn: (event: string, data?: any) => void;
|
|
12
12
|
error: (event: string, data?: any) => any;
|
|
13
13
|
};
|
|
@@ -26,7 +26,9 @@ type LoggerApi = {
|
|
|
26
26
|
warn: (event: string, data?: Error | ({
|
|
27
27
|
error: Error;
|
|
28
28
|
} & Record<string, any>) | unknown, options?: LogOptions) => void;
|
|
29
|
-
info: (event: string, data?:
|
|
29
|
+
info: (event: string, data?: Error | ({
|
|
30
|
+
error: Error;
|
|
31
|
+
} & Record<string, any>) | unknown, options?: LogOptions) => void;
|
|
30
32
|
/**
|
|
31
33
|
* Context passed in will be attached to all entries in this scope.
|
|
32
34
|
*/
|
|
@@ -71,7 +73,9 @@ declare class Logger implements LoggerApi {
|
|
|
71
73
|
warn(event: string, dataOrError?: Error | ({
|
|
72
74
|
error: Error;
|
|
73
75
|
} & Record<string, any>) | unknown, options?: LogOptions): void;
|
|
74
|
-
info(event: string,
|
|
76
|
+
info(event: string, dataOrError?: Error | ({
|
|
77
|
+
error: Error;
|
|
78
|
+
} & Record<string, any>) | unknown, options?: LogOptions): void;
|
|
75
79
|
withScope(scope: string, context?: Record<string, any>): LoggerApi;
|
|
76
80
|
withContext(context: Record<string, any>): LoggerApi;
|
|
77
81
|
withSink(sink: LoggerSink): Logger;
|
|
@@ -144,8 +144,27 @@ class Logger {
|
|
|
144
144
|
options
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
|
-
info(event,
|
|
148
|
-
|
|
147
|
+
info(event, dataOrError, options) {
|
|
148
|
+
const data = dataOrError instanceof Error ? {
|
|
149
|
+
error: {
|
|
150
|
+
type: dataOrError.constructor.name,
|
|
151
|
+
message: dataOrError.message,
|
|
152
|
+
stack: dataOrError.stack || null
|
|
153
|
+
}
|
|
154
|
+
} : isObject(dataOrError) && dataOrError.error instanceof Error ? {
|
|
155
|
+
...dataOrError,
|
|
156
|
+
error: {
|
|
157
|
+
type: dataOrError.error.constructor.name,
|
|
158
|
+
message: dataOrError.error.message,
|
|
159
|
+
stack: dataOrError.error.stack || null
|
|
160
|
+
}
|
|
161
|
+
} : isObject(dataOrError) ? dataOrError : dataOrError !== void 0 ? { error: dataOrError } : void 0;
|
|
162
|
+
this.entry({
|
|
163
|
+
level: "info",
|
|
164
|
+
event,
|
|
165
|
+
data,
|
|
166
|
+
options
|
|
167
|
+
});
|
|
149
168
|
}
|
|
150
169
|
withScope(scope, context = {}) {
|
|
151
170
|
return new Logger([...this.scopes, scope], this.sinks, {
|
package/package.json
CHANGED
|
@@ -43,6 +43,42 @@ export const myWorkflow = workflow<Input, Output>(
|
|
|
43
43
|
- Use `await pause()` (imported from `"libretto"`) to pause the workflow for debugging. It is a no-op in production.
|
|
44
44
|
- The browser is launched and closed automatically by the CLI — do not launch or close it in the handler
|
|
45
45
|
|
|
46
|
+
## Passing Application Dependencies via Services
|
|
47
|
+
|
|
48
|
+
Use the third generic on `workflow<Input, Output, Services>` to inject
|
|
49
|
+
dependencies that exist in your application but not in libretto's runtime
|
|
50
|
+
(DB transactions, API clients, caches, etc.):
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { type Transaction } from "./db";
|
|
54
|
+
|
|
55
|
+
type MyServices = { tx?: Transaction };
|
|
56
|
+
|
|
57
|
+
export const myWorkflow = workflow<Input, Output, MyServices>(
|
|
58
|
+
{},
|
|
59
|
+
async (ctx, input) => {
|
|
60
|
+
if (ctx.services.tx) {
|
|
61
|
+
await ctx.services.tx.insert(/* ... */);
|
|
62
|
+
} else {
|
|
63
|
+
ctx.logger.info("No DB transaction — skipping write");
|
|
64
|
+
}
|
|
65
|
+
// ... browser automation ...
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
In production, the caller passes services when invoking `.run()`:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
await myWorkflow.run(
|
|
74
|
+
{ page, logger, services: { tx } },
|
|
75
|
+
input,
|
|
76
|
+
);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
When running standalone via `npx libretto run`, services defaults to `{}`,
|
|
80
|
+
so mark fields optional for anything unavailable in that context.
|
|
81
|
+
|
|
46
82
|
## Playwright Locators for DOM Interaction
|
|
47
83
|
|
|
48
84
|
Generated code must use Playwright locator APIs for all DOM interactions. Do not use `page.evaluate()` with `document.querySelector`, `querySelectorAll`, `textContent`, `click()`, or other DOM APIs when a Playwright locator can do the same thing.
|