libretto 0.5.0 → 0.5.2

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 (122) hide show
  1. package/README.md +109 -35
  2. package/dist/cli/cli.js +22 -97
  3. package/dist/cli/commands/browser.js +86 -59
  4. package/dist/cli/commands/execution.js +199 -86
  5. package/dist/cli/commands/init.js +34 -29
  6. package/dist/cli/commands/logs.js +4 -5
  7. package/dist/cli/commands/shared.js +30 -29
  8. package/dist/cli/commands/snapshot.js +26 -39
  9. package/dist/cli/core/ai-config.js +21 -4
  10. package/dist/cli/core/api-snapshot-analyzer.js +15 -5
  11. package/dist/cli/core/browser.js +207 -37
  12. package/dist/cli/core/context.js +4 -1
  13. package/dist/cli/core/session-telemetry.js +434 -174
  14. package/dist/cli/core/session.js +21 -8
  15. package/dist/cli/core/snapshot-analyzer.js +14 -31
  16. package/dist/cli/core/snapshot-api-config.js +2 -6
  17. package/dist/cli/core/telemetry.js +20 -4
  18. package/dist/cli/framework/simple-cli.js +45 -25
  19. package/dist/cli/router.js +14 -21
  20. package/dist/cli/workers/run-integration-runtime.js +24 -5
  21. package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
  22. package/dist/cli/workers/run-integration-worker.js +1 -4
  23. package/dist/index.d.ts +1 -2
  24. package/dist/index.js +7 -10
  25. package/dist/runtime/download/download.js +5 -1
  26. package/dist/runtime/extract/extract.js +11 -2
  27. package/dist/runtime/network/network.js +8 -1
  28. package/dist/runtime/recovery/agent.js +6 -2
  29. package/dist/runtime/recovery/errors.js +3 -1
  30. package/dist/runtime/recovery/recovery.js +3 -1
  31. package/dist/shared/condense-dom/condense-dom.js +17 -69
  32. package/dist/shared/config/config.d.ts +1 -9
  33. package/dist/shared/config/config.js +0 -18
  34. package/dist/shared/config/index.d.ts +2 -1
  35. package/dist/shared/config/index.js +0 -10
  36. package/dist/shared/debug/pause.js +9 -3
  37. package/dist/shared/dom-semantics.d.ts +8 -0
  38. package/dist/shared/dom-semantics.js +69 -0
  39. package/dist/shared/instrumentation/instrument.js +101 -5
  40. package/dist/shared/llm/ai-sdk-adapter.js +3 -1
  41. package/dist/shared/llm/client.js +3 -1
  42. package/dist/shared/logger/index.js +4 -1
  43. package/dist/shared/run/api.js +3 -1
  44. package/dist/shared/run/browser.js +47 -3
  45. package/dist/shared/state/session-state.d.ts +2 -1
  46. package/dist/shared/state/session-state.js +5 -2
  47. package/dist/shared/visualization/ghost-cursor.js +36 -14
  48. package/dist/shared/visualization/highlight.js +9 -6
  49. package/dist/shared/workflow/workflow.d.ts +4 -5
  50. package/dist/shared/workflow/workflow.js +3 -5
  51. package/package.json +6 -2
  52. package/scripts/check-skills-sync.mjs +25 -0
  53. package/scripts/compare-eval-summary.mjs +47 -0
  54. package/scripts/postinstall.mjs +15 -15
  55. package/scripts/prepare-release.sh +97 -0
  56. package/scripts/skills-libretto.mjs +103 -0
  57. package/scripts/summarize-evals.mjs +135 -0
  58. package/scripts/sync-skills.mjs +12 -0
  59. package/skills/libretto/SKILL.md +132 -54
  60. package/skills/libretto/references/action-logs.md +101 -0
  61. package/skills/libretto/references/auth-profiles.md +1 -2
  62. package/skills/libretto/references/code-generation-rules.md +210 -0
  63. package/skills/libretto/references/configuration-file-reference.md +53 -0
  64. package/skills/libretto/references/pages-and-page-targeting.md +1 -1
  65. package/skills/libretto/references/site-security-review.md +143 -0
  66. package/src/cli/cli.ts +23 -110
  67. package/src/cli/commands/browser.ts +94 -70
  68. package/src/cli/commands/execution.ts +233 -102
  69. package/src/cli/commands/init.ts +37 -33
  70. package/src/cli/commands/logs.ts +7 -7
  71. package/src/cli/commands/shared.ts +36 -37
  72. package/src/cli/commands/snapshot.ts +44 -59
  73. package/src/cli/core/ai-config.ts +24 -4
  74. package/src/cli/core/api-snapshot-analyzer.ts +17 -6
  75. package/src/cli/core/browser.ts +260 -49
  76. package/src/cli/core/context.ts +7 -2
  77. package/src/cli/core/session-telemetry.ts +449 -197
  78. package/src/cli/core/session.ts +21 -7
  79. package/src/cli/core/snapshot-analyzer.ts +26 -46
  80. package/src/cli/core/snapshot-api-config.ts +170 -175
  81. package/src/cli/core/telemetry.ts +39 -4
  82. package/src/cli/framework/simple-cli.ts +144 -77
  83. package/src/cli/router.ts +13 -21
  84. package/src/cli/workers/run-integration-runtime.ts +36 -9
  85. package/src/cli/workers/run-integration-worker-protocol.ts +2 -0
  86. package/src/cli/workers/run-integration-worker.ts +1 -4
  87. package/src/index.ts +73 -66
  88. package/src/runtime/download/download.ts +62 -58
  89. package/src/runtime/download/index.ts +5 -5
  90. package/src/runtime/extract/extract.ts +71 -61
  91. package/src/runtime/network/index.ts +3 -3
  92. package/src/runtime/network/network.ts +99 -93
  93. package/src/runtime/recovery/agent.ts +217 -212
  94. package/src/runtime/recovery/errors.ts +107 -104
  95. package/src/runtime/recovery/index.ts +3 -3
  96. package/src/runtime/recovery/recovery.ts +38 -35
  97. package/src/shared/condense-dom/condense-dom.ts +27 -82
  98. package/src/shared/config/config.ts +0 -19
  99. package/src/shared/config/index.ts +0 -5
  100. package/src/shared/debug/pause.ts +57 -51
  101. package/src/shared/dom-semantics.ts +68 -0
  102. package/src/shared/instrumentation/errors.ts +64 -62
  103. package/src/shared/instrumentation/index.ts +5 -5
  104. package/src/shared/instrumentation/instrument.ts +339 -209
  105. package/src/shared/llm/ai-sdk-adapter.ts +58 -55
  106. package/src/shared/llm/client.ts +181 -174
  107. package/src/shared/llm/types.ts +39 -39
  108. package/src/shared/logger/index.ts +11 -4
  109. package/src/shared/logger/logger.ts +312 -306
  110. package/src/shared/logger/sinks.ts +118 -114
  111. package/src/shared/paths/paths.ts +50 -49
  112. package/src/shared/paths/repo-root.ts +17 -17
  113. package/src/shared/run/api.ts +5 -1
  114. package/src/shared/run/browser.ts +65 -3
  115. package/src/shared/state/index.ts +9 -9
  116. package/src/shared/state/session-state.ts +46 -43
  117. package/src/shared/visualization/ghost-cursor.ts +180 -149
  118. package/src/shared/visualization/highlight.ts +89 -86
  119. package/src/shared/visualization/index.ts +13 -13
  120. package/src/shared/workflow/workflow.ts +19 -25
  121. package/skills/libretto/references/reverse-engineering-network-requests.md +0 -39
  122. package/skills/libretto/references/user-action-log.md +0 -31
@@ -18,61 +18,64 @@ import type { LLMClient, Message } from "./types.js";
18
18
  * ```
19
19
  */
20
20
  export function createLLMClientFromModel(model: LanguageModel): LLMClient {
21
- return {
22
- async generateObject<T extends ZodType>(opts: {
23
- prompt: string;
24
- schema: T;
25
- temperature?: number;
26
- }): Promise<ZodOutput<T>> {
27
- const { object } = await generateObject({
28
- model,
29
- schema: opts.schema,
30
- prompt: opts.prompt,
31
- temperature: opts.temperature ?? 0,
32
- });
33
- return object as ZodOutput<T>;
34
- },
21
+ return {
22
+ async generateObject<T extends ZodType>(opts: {
23
+ prompt: string;
24
+ schema: T;
25
+ temperature?: number;
26
+ }): Promise<ZodOutput<T>> {
27
+ const { object } = await generateObject({
28
+ model,
29
+ schema: opts.schema,
30
+ prompt: opts.prompt,
31
+ temperature: opts.temperature ?? 0,
32
+ });
33
+ return object as ZodOutput<T>;
34
+ },
35
35
 
36
- async generateObjectFromMessages<T extends ZodType>(opts: {
37
- messages: Message[];
38
- schema: T;
39
- temperature?: number;
40
- }): Promise<ZodOutput<T>> {
41
- // Convert libretto Message format to AI SDK message format
42
- const messages = opts.messages.map((msg) => {
43
- if (typeof msg.content === "string") {
44
- return { role: msg.role, content: msg.content };
45
- }
46
- if (msg.role === "assistant") {
47
- // AssistantContent only supports text parts (no images)
48
- return {
49
- role: "assistant" as const,
50
- content: msg.content
51
- .filter((part): part is typeof part & { type: "text" } => part.type === "text")
52
- .map((part) => ({ type: "text" as const, text: part.text })),
53
- };
54
- }
55
- return {
56
- role: "user" as const,
57
- content: msg.content.map((part) =>
58
- part.type === "text"
59
- ? { type: "text" as const, text: part.text }
60
- : {
61
- type: "image" as const,
62
- image: part.image,
63
- ...(part.mediaType ? { mediaType: part.mediaType } : {}),
64
- },
65
- ),
66
- };
67
- });
36
+ async generateObjectFromMessages<T extends ZodType>(opts: {
37
+ messages: Message[];
38
+ schema: T;
39
+ temperature?: number;
40
+ }): Promise<ZodOutput<T>> {
41
+ // Convert libretto Message format to AI SDK message format
42
+ const messages = opts.messages.map((msg) => {
43
+ if (typeof msg.content === "string") {
44
+ return { role: msg.role, content: msg.content };
45
+ }
46
+ if (msg.role === "assistant") {
47
+ // AssistantContent only supports text parts (no images)
48
+ return {
49
+ role: "assistant" as const,
50
+ content: msg.content
51
+ .filter(
52
+ (part): part is typeof part & { type: "text" } =>
53
+ part.type === "text",
54
+ )
55
+ .map((part) => ({ type: "text" as const, text: part.text })),
56
+ };
57
+ }
58
+ return {
59
+ role: "user" as const,
60
+ content: msg.content.map((part) =>
61
+ part.type === "text"
62
+ ? { type: "text" as const, text: part.text }
63
+ : {
64
+ type: "image" as const,
65
+ image: part.image,
66
+ ...(part.mediaType ? { mediaType: part.mediaType } : {}),
67
+ },
68
+ ),
69
+ };
70
+ });
68
71
 
69
- const { object } = await generateObject({
70
- model,
71
- schema: opts.schema,
72
- messages,
73
- temperature: opts.temperature ?? 0,
74
- });
75
- return object as ZodOutput<T>;
76
- },
77
- };
72
+ const { object } = await generateObject({
73
+ model,
74
+ schema: opts.schema,
75
+ messages,
76
+ temperature: opts.temperature ?? 0,
77
+ });
78
+ return object as ZodOutput<T>;
79
+ },
80
+ };
78
81
  }
@@ -5,213 +5,220 @@ import type { LLMClient, Message, MessageContentPart } from "./types.js";
5
5
  export type Provider = "google" | "vertex" | "anthropic" | "openai";
6
6
 
7
7
  const GEMINI_API_KEY_ENV_VARS = [
8
- "GEMINI_API_KEY",
9
- "GOOGLE_GENERATIVE_AI_API_KEY",
8
+ "GEMINI_API_KEY",
9
+ "GOOGLE_GENERATIVE_AI_API_KEY",
10
10
  ] as const;
11
11
 
12
12
  const VERTEX_PROJECT_ENV_VARS = [
13
- "GOOGLE_CLOUD_PROJECT",
14
- "GCLOUD_PROJECT",
13
+ "GOOGLE_CLOUD_PROJECT",
14
+ "GCLOUD_PROJECT",
15
15
  ] as const;
16
16
 
17
17
  const SUPPORTED_PROVIDER_ALIASES = {
18
- google: "google",
19
- gemini: "google",
20
- vertex: "vertex",
21
- anthropic: "anthropic",
22
- codex: "openai",
23
- openai: "openai",
18
+ google: "google",
19
+ gemini: "google",
20
+ vertex: "vertex",
21
+ anthropic: "anthropic",
22
+ codex: "openai",
23
+ openai: "openai",
24
24
  } as const satisfies Record<string, Provider>;
25
25
 
26
26
  function readFirstEnvValue(
27
- env: NodeJS.ProcessEnv,
28
- names: readonly string[],
27
+ env: NodeJS.ProcessEnv,
28
+ names: readonly string[],
29
29
  ): string | null {
30
- for (const name of names) {
31
- const value = env[name]?.trim();
32
- if (value) return value;
33
- }
34
- return null;
30
+ for (const name of names) {
31
+ const value = env[name]?.trim();
32
+ if (value) return value;
33
+ }
34
+ return null;
35
35
  }
36
36
 
37
- export function parseModel(model: string): { provider: Provider; modelId: string } {
38
- const slashIndex = model.indexOf("/");
39
- if (slashIndex === -1) {
40
- throw new Error(
41
- `Invalid model string "${model}". Expected format: "provider/model-id" (for example "openai/gpt-5.4", "anthropic/claude-sonnet-4-6", "google/gemini-3-flash-preview", or "vertex/gemini-2.5-pro").`,
42
- );
43
- }
44
- const providerInput = model.slice(0, slashIndex).toLowerCase();
45
- const provider = SUPPORTED_PROVIDER_ALIASES[
46
- providerInput as keyof typeof SUPPORTED_PROVIDER_ALIASES
47
- ];
48
- const modelId = model.slice(slashIndex + 1);
49
-
50
- if (!provider) {
51
- throw new Error(
52
- `Unsupported provider "${providerInput}". Supported providers: openai/codex, anthropic, google (Gemini API), and vertex.`,
53
- );
54
- }
55
-
56
- return { provider, modelId };
37
+ export function parseModel(model: string): {
38
+ provider: Provider;
39
+ modelId: string;
40
+ } {
41
+ const slashIndex = model.indexOf("/");
42
+ if (slashIndex === -1) {
43
+ throw new Error(
44
+ `Invalid model string "${model}". Expected format: "provider/model-id" (for example "openai/gpt-5.4", "anthropic/claude-sonnet-4-6", "google/gemini-3-flash-preview", or "vertex/gemini-2.5-pro").`,
45
+ );
46
+ }
47
+ const providerInput = model.slice(0, slashIndex).toLowerCase();
48
+ const provider =
49
+ SUPPORTED_PROVIDER_ALIASES[
50
+ providerInput as keyof typeof SUPPORTED_PROVIDER_ALIASES
51
+ ];
52
+ const modelId = model.slice(slashIndex + 1);
53
+
54
+ if (!provider) {
55
+ throw new Error(
56
+ `Unsupported provider "${providerInput}". Supported providers: openai/codex, anthropic, google (Gemini API), and vertex.`,
57
+ );
58
+ }
59
+
60
+ return { provider, modelId };
57
61
  }
58
62
 
59
63
  export function hasProviderCredentials(
60
- provider: Provider,
61
- env: NodeJS.ProcessEnv = process.env,
64
+ provider: Provider,
65
+ env: NodeJS.ProcessEnv = process.env,
62
66
  ): boolean {
63
- switch (provider) {
64
- case "google":
65
- return readFirstEnvValue(env, GEMINI_API_KEY_ENV_VARS) !== null;
66
- case "vertex":
67
- return readFirstEnvValue(env, VERTEX_PROJECT_ENV_VARS) !== null;
68
- case "anthropic":
69
- return Boolean(env.ANTHROPIC_API_KEY?.trim());
70
- case "openai":
71
- return Boolean(env.OPENAI_API_KEY?.trim());
72
- }
67
+ switch (provider) {
68
+ case "google":
69
+ return readFirstEnvValue(env, GEMINI_API_KEY_ENV_VARS) !== null;
70
+ case "vertex":
71
+ return readFirstEnvValue(env, VERTEX_PROJECT_ENV_VARS) !== null;
72
+ case "anthropic":
73
+ return Boolean(env.ANTHROPIC_API_KEY?.trim());
74
+ case "openai":
75
+ return Boolean(env.OPENAI_API_KEY?.trim());
76
+ }
73
77
  }
74
78
 
75
79
  export function missingProviderCredentialsMessage(provider: Provider): string {
76
- switch (provider) {
77
- case "google":
78
- return "Gemini API key is missing. Set GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY.";
79
- case "vertex":
80
- return "Vertex AI project is missing. Set GOOGLE_CLOUD_PROJECT (or GCLOUD_PROJECT) and ensure application default credentials are configured.";
81
- case "anthropic": {
82
- return "Anthropic API key is missing. Set ANTHROPIC_API_KEY.";
83
- }
84
- case "openai": {
85
- return "OpenAI API key is missing. Set OPENAI_API_KEY.";
86
- }
87
- }
80
+ switch (provider) {
81
+ case "google":
82
+ return "Gemini API key is missing. Set GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY.";
83
+ case "vertex":
84
+ return "Vertex AI project is missing. Set GOOGLE_CLOUD_PROJECT (or GCLOUD_PROJECT) and ensure application default credentials are configured.";
85
+ case "anthropic": {
86
+ return "Anthropic API key is missing. Set ANTHROPIC_API_KEY.";
87
+ }
88
+ case "openai": {
89
+ return "OpenAI API key is missing. Set OPENAI_API_KEY.";
90
+ }
91
+ }
88
92
  }
89
93
 
90
94
  async function getProviderModel(
91
- provider: Provider,
92
- modelId: string,
95
+ provider: Provider,
96
+ modelId: string,
93
97
  ): Promise<LanguageModel> {
94
- switch (provider) {
95
- case "google": {
96
- const apiKey = readFirstEnvValue(process.env, GEMINI_API_KEY_ENV_VARS);
97
- if (!apiKey) {
98
- throw new Error(missingProviderCredentialsMessage(provider));
99
- }
100
- const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
101
- const google = createGoogleGenerativeAI({ apiKey });
102
- return google(modelId);
103
- }
104
- case "vertex": {
105
- const project = readFirstEnvValue(process.env, VERTEX_PROJECT_ENV_VARS);
106
- if (!project) {
107
- throw new Error(missingProviderCredentialsMessage(provider));
108
- }
109
- const { createVertex } = await import("@ai-sdk/google-vertex");
110
- const vertex = createVertex({
111
- project,
112
- location: process.env.GOOGLE_CLOUD_LOCATION || "global",
113
- });
114
- return vertex(modelId);
115
- }
116
- case "anthropic": {
117
- const apiKey = process.env.ANTHROPIC_API_KEY?.trim();
118
- if (!apiKey) {
119
- throw new Error(missingProviderCredentialsMessage(provider));
120
- }
121
- const { createAnthropic } = await import("@ai-sdk/anthropic");
122
- const anthropic = createAnthropic({ apiKey });
123
- return anthropic(modelId);
124
- }
125
- case "openai": {
126
- const apiKey = process.env.OPENAI_API_KEY?.trim();
127
- if (!apiKey) {
128
- throw new Error(missingProviderCredentialsMessage(provider));
129
- }
130
- const { createOpenAI } = await import("@ai-sdk/openai");
131
- const openai = createOpenAI({ apiKey });
132
- return openai(modelId);
133
- }
134
- }
98
+ switch (provider) {
99
+ case "google": {
100
+ const apiKey = readFirstEnvValue(process.env, GEMINI_API_KEY_ENV_VARS);
101
+ if (!apiKey) {
102
+ throw new Error(missingProviderCredentialsMessage(provider));
103
+ }
104
+ const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
105
+ const google = createGoogleGenerativeAI({ apiKey });
106
+ return google(modelId);
107
+ }
108
+ case "vertex": {
109
+ const project = readFirstEnvValue(process.env, VERTEX_PROJECT_ENV_VARS);
110
+ if (!project) {
111
+ throw new Error(missingProviderCredentialsMessage(provider));
112
+ }
113
+ const { createVertex } = await import("@ai-sdk/google-vertex");
114
+ const vertex = createVertex({
115
+ project,
116
+ location: process.env.GOOGLE_CLOUD_LOCATION || "global",
117
+ });
118
+ return vertex(modelId);
119
+ }
120
+ case "anthropic": {
121
+ const apiKey = process.env.ANTHROPIC_API_KEY?.trim();
122
+ if (!apiKey) {
123
+ throw new Error(missingProviderCredentialsMessage(provider));
124
+ }
125
+ const { createAnthropic } = await import("@ai-sdk/anthropic");
126
+ const anthropic = createAnthropic({ apiKey });
127
+ return anthropic(modelId);
128
+ }
129
+ case "openai": {
130
+ const apiKey = process.env.OPENAI_API_KEY?.trim();
131
+ if (!apiKey) {
132
+ throw new Error(missingProviderCredentialsMessage(provider));
133
+ }
134
+ const { createOpenAI } = await import("@ai-sdk/openai");
135
+ const openai = createOpenAI({ apiKey });
136
+ return openai(modelId);
137
+ }
138
+ }
135
139
  }
136
140
 
137
141
  function convertUserContentParts(parts: MessageContentPart[]) {
138
- return parts.map((part) => {
139
- if (part.type === "text") {
140
- return { type: "text" as const, text: part.text };
141
- }
142
- return {
143
- type: "image" as const,
144
- image: part.image,
145
- ...(part.mediaType ? { mediaType: part.mediaType } : {}),
146
- };
147
- });
142
+ return parts.map((part) => {
143
+ if (part.type === "text") {
144
+ return { type: "text" as const, text: part.text };
145
+ }
146
+ return {
147
+ type: "image" as const,
148
+ image: part.image,
149
+ ...(part.mediaType ? { mediaType: part.mediaType } : {}),
150
+ };
151
+ });
148
152
  }
149
153
 
150
154
  function convertAssistantContentParts(parts: MessageContentPart[]) {
151
- return parts
152
- .filter((part): part is MessageContentPart & { type: "text" } => part.type === "text")
153
- .map((part) => ({ type: "text" as const, text: part.text }));
155
+ return parts
156
+ .filter(
157
+ (part): part is MessageContentPart & { type: "text" } =>
158
+ part.type === "text",
159
+ )
160
+ .map((part) => ({ type: "text" as const, text: part.text }));
154
161
  }
155
162
 
156
163
  function convertMessages(messages: Message[]): ModelMessage[] {
157
- return messages.map((msg): ModelMessage => {
158
- if (msg.role === "user") {
159
- if (typeof msg.content === "string") {
160
- return { role: "user", content: msg.content };
161
- }
162
- return {
163
- role: "user",
164
- content: convertUserContentParts(msg.content),
165
- };
166
- }
167
- if (typeof msg.content === "string") {
168
- return { role: "assistant", content: msg.content };
169
- }
170
- return {
171
- role: "assistant",
172
- content: convertAssistantContentParts(msg.content),
173
- };
174
- });
164
+ return messages.map((msg): ModelMessage => {
165
+ if (msg.role === "user") {
166
+ if (typeof msg.content === "string") {
167
+ return { role: "user", content: msg.content };
168
+ }
169
+ return {
170
+ role: "user",
171
+ content: convertUserContentParts(msg.content),
172
+ };
173
+ }
174
+ if (typeof msg.content === "string") {
175
+ return { role: "assistant", content: msg.content };
176
+ }
177
+ return {
178
+ role: "assistant",
179
+ content: convertAssistantContentParts(msg.content),
180
+ };
181
+ });
175
182
  }
176
183
 
177
184
  export function createLLMClient(model: string): LLMClient {
178
- const { provider, modelId } = parseModel(model);
179
- let modelPromise: Promise<LanguageModel> | null = null;
180
-
181
- const getModel = () => {
182
- modelPromise ??= getProviderModel(provider, modelId);
183
- return modelPromise;
184
- };
185
-
186
- return {
187
- async generateObject<T extends ZodType>(opts: {
188
- prompt: string;
189
- schema: T;
190
- temperature?: number;
191
- }): Promise<ZodOutput<T>> {
192
- const aiModel = await getModel();
193
- const result = await generateObject({
194
- model: aiModel,
195
- prompt: opts.prompt,
196
- schema: opts.schema,
197
- temperature: opts.temperature ?? 0,
198
- });
199
- return result.object as ZodOutput<T>;
200
- },
201
-
202
- async generateObjectFromMessages<T extends ZodType>(opts: {
203
- messages: Message[];
204
- schema: T;
205
- temperature?: number;
206
- }): Promise<ZodOutput<T>> {
207
- const aiModel = await getModel();
208
- const result = await generateObject({
209
- model: aiModel,
210
- messages: convertMessages(opts.messages),
211
- schema: opts.schema,
212
- temperature: opts.temperature ?? 0,
213
- });
214
- return result.object as ZodOutput<T>;
215
- },
216
- };
185
+ const { provider, modelId } = parseModel(model);
186
+ let modelPromise: Promise<LanguageModel> | null = null;
187
+
188
+ const getModel = () => {
189
+ modelPromise ??= getProviderModel(provider, modelId);
190
+ return modelPromise;
191
+ };
192
+
193
+ return {
194
+ async generateObject<T extends ZodType>(opts: {
195
+ prompt: string;
196
+ schema: T;
197
+ temperature?: number;
198
+ }): Promise<ZodOutput<T>> {
199
+ const aiModel = await getModel();
200
+ const result = await generateObject({
201
+ model: aiModel,
202
+ prompt: opts.prompt,
203
+ schema: opts.schema,
204
+ temperature: opts.temperature ?? 0,
205
+ });
206
+ return result.object as ZodOutput<T>;
207
+ },
208
+
209
+ async generateObjectFromMessages<T extends ZodType>(opts: {
210
+ messages: Message[];
211
+ schema: T;
212
+ temperature?: number;
213
+ }): Promise<ZodOutput<T>> {
214
+ const aiModel = await getModel();
215
+ const result = await generateObject({
216
+ model: aiModel,
217
+ messages: convertMessages(opts.messages),
218
+ schema: opts.schema,
219
+ temperature: opts.temperature ?? 0,
220
+ });
221
+ return result.object as ZodOutput<T>;
222
+ },
223
+ };
217
224
  }
@@ -1,12 +1,12 @@
1
1
  import type z from "zod";
2
2
 
3
3
  export type MessageContentPart =
4
- | { type: "text"; text: string }
5
- | { type: "image"; image: string | Uint8Array; mediaType?: string };
4
+ | { type: "text"; text: string }
5
+ | { type: "image"; image: string | Uint8Array; mediaType?: string };
6
6
 
7
7
  export type Message = {
8
- role: "user" | "assistant";
9
- content: string | MessageContentPart[];
8
+ role: "user" | "assistant";
9
+ content: string | MessageContentPart[];
10
10
  };
11
11
 
12
12
  /**
@@ -24,40 +24,40 @@ export type Message = {
24
24
  * {@link createLLMClientFromModel} in `libretto/llm`.
25
25
  */
26
26
  export interface LLMClient {
27
- /**
28
- * Generate a structured object from a single text prompt.
29
- *
30
- * The underlying model **must** support structured / JSON output so that
31
- * the response can be parsed and validated against the provided Zod schema.
32
- *
33
- * @param opts.prompt - The text prompt sent to the model.
34
- * @param opts.schema - A Zod schema describing the expected response shape.
35
- * @param opts.temperature - Sampling temperature (default chosen by implementation, typically 0).
36
- * @returns The parsed object matching the schema.
37
- * @throws On LLM or parsing failure.
38
- */
39
- generateObject<T extends z.ZodType>(opts: {
40
- prompt: string;
41
- schema: T;
42
- temperature?: number;
43
- }): Promise<z.output<T>>;
27
+ /**
28
+ * Generate a structured object from a single text prompt.
29
+ *
30
+ * The underlying model **must** support structured / JSON output so that
31
+ * the response can be parsed and validated against the provided Zod schema.
32
+ *
33
+ * @param opts.prompt - The text prompt sent to the model.
34
+ * @param opts.schema - A Zod schema describing the expected response shape.
35
+ * @param opts.temperature - Sampling temperature (default chosen by implementation, typically 0).
36
+ * @returns The parsed object matching the schema.
37
+ * @throws On LLM or parsing failure.
38
+ */
39
+ generateObject<T extends z.ZodType>(opts: {
40
+ prompt: string;
41
+ schema: T;
42
+ temperature?: number;
43
+ }): Promise<z.output<T>>;
44
44
 
45
- /**
46
- * Generate a structured object from a conversation-style message array.
47
- *
48
- * Messages may contain **image content** (base64 data URIs via
49
- * {@link MessageContentPart}), so the backing model must support
50
- * vision / multimodal input when images are present.
51
- *
52
- * @param opts.messages - Ordered list of user/assistant messages, potentially multimodal.
53
- * @param opts.schema - A Zod schema describing the expected response shape.
54
- * @param opts.temperature - Sampling temperature (default chosen by implementation, typically 0).
55
- * @returns The parsed object matching the schema.
56
- * @throws On LLM or parsing failure.
57
- */
58
- generateObjectFromMessages<T extends z.ZodType>(opts: {
59
- messages: Message[];
60
- schema: T;
61
- temperature?: number;
62
- }): Promise<z.output<T>>;
45
+ /**
46
+ * Generate a structured object from a conversation-style message array.
47
+ *
48
+ * Messages may contain **image content** (base64 data URIs via
49
+ * {@link MessageContentPart}), so the backing model must support
50
+ * vision / multimodal input when images are present.
51
+ *
52
+ * @param opts.messages - Ordered list of user/assistant messages, potentially multimodal.
53
+ * @param opts.schema - A Zod schema describing the expected response shape.
54
+ * @param opts.temperature - Sampling temperature (default chosen by implementation, typically 0).
55
+ * @returns The parsed object matching the schema.
56
+ * @throws On LLM or parsing failure.
57
+ */
58
+ generateObjectFromMessages<T extends z.ZodType>(opts: {
59
+ messages: Message[];
60
+ schema: T;
61
+ temperature?: number;
62
+ }): Promise<z.output<T>>;
63
63
  }
@@ -1,6 +1,13 @@
1
- export { Logger, defaultLogger, type LoggerApi, type MinimalLogger, type LoggerSink, type LogOptions } from "./logger.js";
2
1
  export {
3
- createFileLogSink,
4
- prettyConsoleSink,
5
- jsonlConsoleSink,
2
+ Logger,
3
+ defaultLogger,
4
+ type LoggerApi,
5
+ type MinimalLogger,
6
+ type LoggerSink,
7
+ type LogOptions,
8
+ } from "./logger.js";
9
+ export {
10
+ createFileLogSink,
11
+ prettyConsoleSink,
12
+ jsonlConsoleSink,
6
13
  } from "./sinks.js";