@ank1015/llm-agents 0.0.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 (104) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +117 -0
  4. package/dist/agents/skills/index.d.ts +37 -0
  5. package/dist/agents/skills/index.d.ts.map +1 -0
  6. package/dist/agents/skills/index.js +386 -0
  7. package/dist/agents/skills/index.js.map +1 -0
  8. package/dist/agents/system-prompt.d.ts +7 -0
  9. package/dist/agents/system-prompt.d.ts.map +1 -0
  10. package/dist/agents/system-prompt.js +106 -0
  11. package/dist/agents/system-prompt.js.map +1 -0
  12. package/dist/agents/tools.d.ts +3 -0
  13. package/dist/agents/tools.d.ts.map +1 -0
  14. package/dist/agents/tools.js +3 -0
  15. package/dist/agents/tools.js.map +1 -0
  16. package/dist/helpers/ai-image/ai-image.d.ts +75 -0
  17. package/dist/helpers/ai-image/ai-image.d.ts.map +1 -0
  18. package/dist/helpers/ai-image/ai-image.js +438 -0
  19. package/dist/helpers/ai-image/ai-image.js.map +1 -0
  20. package/dist/helpers/ai-image/index.d.ts +3 -0
  21. package/dist/helpers/ai-image/index.d.ts.map +1 -0
  22. package/dist/helpers/ai-image/index.js +2 -0
  23. package/dist/helpers/ai-image/index.js.map +1 -0
  24. package/dist/helpers/index.d.ts +2 -0
  25. package/dist/helpers/index.d.ts.map +1 -0
  26. package/dist/helpers/index.js +2 -0
  27. package/dist/helpers/index.js.map +1 -0
  28. package/dist/index.d.ts +12 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +12 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/tools/bash.d.ts +55 -0
  33. package/dist/tools/bash.d.ts.map +1 -0
  34. package/dist/tools/bash.js +246 -0
  35. package/dist/tools/bash.js.map +1 -0
  36. package/dist/tools/edit-diff.d.ts +63 -0
  37. package/dist/tools/edit-diff.d.ts.map +1 -0
  38. package/dist/tools/edit-diff.js +246 -0
  39. package/dist/tools/edit-diff.js.map +1 -0
  40. package/dist/tools/edit.d.ts +39 -0
  41. package/dist/tools/edit.d.ts.map +1 -0
  42. package/dist/tools/edit.js +150 -0
  43. package/dist/tools/edit.js.map +1 -0
  44. package/dist/tools/find.d.ts +39 -0
  45. package/dist/tools/find.d.ts.map +1 -0
  46. package/dist/tools/find.js +206 -0
  47. package/dist/tools/find.js.map +1 -0
  48. package/dist/tools/grep.d.ts +45 -0
  49. package/dist/tools/grep.d.ts.map +1 -0
  50. package/dist/tools/grep.js +244 -0
  51. package/dist/tools/grep.js.map +1 -0
  52. package/dist/tools/index.d.ts +73 -0
  53. package/dist/tools/index.d.ts.map +1 -0
  54. package/dist/tools/index.js +66 -0
  55. package/dist/tools/index.js.map +1 -0
  56. package/dist/tools/ls.d.ts +40 -0
  57. package/dist/tools/ls.d.ts.map +1 -0
  58. package/dist/tools/ls.js +121 -0
  59. package/dist/tools/ls.js.map +1 -0
  60. package/dist/tools/path-utils.d.ts +8 -0
  61. package/dist/tools/path-utils.d.ts.map +1 -0
  62. package/dist/tools/path-utils.js +81 -0
  63. package/dist/tools/path-utils.js.map +1 -0
  64. package/dist/tools/read.d.ts +39 -0
  65. package/dist/tools/read.d.ts.map +1 -0
  66. package/dist/tools/read.js +177 -0
  67. package/dist/tools/read.js.map +1 -0
  68. package/dist/tools/truncate.d.ts +70 -0
  69. package/dist/tools/truncate.d.ts.map +1 -0
  70. package/dist/tools/truncate.js +205 -0
  71. package/dist/tools/truncate.js.map +1 -0
  72. package/dist/tools/utils/config.d.ts +16 -0
  73. package/dist/tools/utils/config.d.ts.map +1 -0
  74. package/dist/tools/utils/config.js +40 -0
  75. package/dist/tools/utils/config.js.map +1 -0
  76. package/dist/tools/utils/image-resize.d.ts +36 -0
  77. package/dist/tools/utils/image-resize.d.ts.map +1 -0
  78. package/dist/tools/utils/image-resize.js +183 -0
  79. package/dist/tools/utils/image-resize.js.map +1 -0
  80. package/dist/tools/utils/mime.d.ts +3 -0
  81. package/dist/tools/utils/mime.d.ts.map +1 -0
  82. package/dist/tools/utils/mime.js +41 -0
  83. package/dist/tools/utils/mime.js.map +1 -0
  84. package/dist/tools/utils/photon.d.ts +21 -0
  85. package/dist/tools/utils/photon.d.ts.map +1 -0
  86. package/dist/tools/utils/photon.js +121 -0
  87. package/dist/tools/utils/photon.js.map +1 -0
  88. package/dist/tools/utils/shell.d.ts +26 -0
  89. package/dist/tools/utils/shell.d.ts.map +1 -0
  90. package/dist/tools/utils/shell.js +177 -0
  91. package/dist/tools/utils/shell.js.map +1 -0
  92. package/dist/tools/utils/tools-manager.d.ts +3 -0
  93. package/dist/tools/utils/tools-manager.d.ts.map +1 -0
  94. package/dist/tools/utils/tools-manager.js +209 -0
  95. package/dist/tools/utils/tools-manager.js.map +1 -0
  96. package/dist/tools/write.d.ts +29 -0
  97. package/dist/tools/write.d.ts.map +1 -0
  98. package/dist/tools/write.js +83 -0
  99. package/dist/tools/write.js.map +1 -0
  100. package/package.json +73 -0
  101. package/skills/ai-images/SKILL.md +45 -0
  102. package/skills/ai-images/references/create.md +106 -0
  103. package/skills/ai-images/references/edit.md +107 -0
  104. package/skills/registry.json +10 -0
@@ -0,0 +1,106 @@
1
+ import { join } from 'node:path';
2
+ import { listInstalledSkills } from './skills/index.js';
3
+ export async function createSystemPrompt({ projectName, projectDir, artifactName, artifactDir, }) {
4
+ const now = new Date();
5
+ const artifactMaxDir = join(artifactDir, '.max');
6
+ const artifactSkillsDir = join(artifactMaxDir, 'skills');
7
+ const artifactTempDir = join(artifactMaxDir, 'temp');
8
+ const dateTime = now.toLocaleString('en-US', {
9
+ weekday: 'long',
10
+ year: 'numeric',
11
+ month: 'long',
12
+ day: 'numeric',
13
+ });
14
+ const availableSkills = await formatInstalledSkills(artifactDir);
15
+ const prompt = `You are Max. Max is an intelligent assistant. Max is an expert generalist and helps the user with all sorts of tasks. Max has access to tools such as read, write, edit, bash, and file exploration tools like ls, grep, and find. Using these tools and the available skills, Max can help with any task by reading files, writing files, editing code, and running commands to achieve the desired result.
16
+
17
+ <tools>
18
+ - read: Read file contents
19
+ - bash: Execute bash commands
20
+ - edit: Make precise edits to files by replacing exact text
21
+ - write: Create or overwrite files
22
+ - grep: Search file contents for patterns (respects .gitignore)
23
+ - find: Find files by glob pattern (respects .gitignore)
24
+ - ls: List directory contents
25
+ </tools>
26
+
27
+ <tools_guidelines>
28
+ - Prefer grep, find, and ls over bash for file exploration because they are faster and respect .gitignore.
29
+ - Use read to inspect files before editing them. Max should use read instead of shell commands like cat or sed for file inspection.
30
+ - Use edit for precise changes when the existing text is known.
31
+ - Use write only for creating new files or completely rewriting a file.
32
+ - Before making changes, Max should read the relevant files and understand the surrounding context.
33
+ - When helpful, Max should verify changes by reading the updated file or running an appropriate command.
34
+ - When summarizing actions, Max should respond in plain text and should not use bash to print the summary.
35
+ - Max should be concise in responses.
36
+ - Max should show file paths clearly when working with files.
37
+ </tools_guidelines>
38
+
39
+ <skills>
40
+ - Max is a generalist and can help with any kind of task. Skills help Max perform specialized tasks in the way the user expects.
41
+ - A skill is an artifact-local folder containing a SKILL.md plus optional scripts, references, or assets.
42
+ - Only the skills listed below are available for this artifact.
43
+ - The user may explicitly mention a skill during a conversation, or Max may decide to load a relevant skill from the available skills list.
44
+ - When a task matches a skill's description, Max should use the read tool to read the SKILL.md at the listed path before proceeding.
45
+ - Max should treat SKILL.md as the overview and then read only the specific reference files, scripts, or assets needed for the current task.
46
+ - When a skill references relative paths, Max should resolve them against the skill directory (the parent directory of SKILL.md) and use absolute paths in tool calls.
47
+ - Max should load only the specific scripts, references, or assets needed for the current task.
48
+ - If a skill bundles executable scripts, Max should prefer running those scripts before writing new helper code.
49
+ - Some skills are helper-backed and teach Max to import functions from \`@ank1015/llm-agents\`.
50
+ - When a helper-backed skill applies, Max may use those helpers either from code written in the artifact project or from the temp workspace under \`${artifactTempDir}\`, depending on what best fits the task.
51
+ - If a relevant skill applies, Max should trust it and follow it closely unless it conflicts with the user's explicit instructions or the available tools.
52
+ <available_skills>
53
+ ${availableSkills}
54
+ </available_skills>
55
+ </skills>
56
+
57
+ <project_information>
58
+ - The user is currently working in the Project named: ${projectName} and the Artifact named: ${artifactName}.
59
+ - A Project is a top-level folder that contains related Artifacts.
60
+ - An Artifact is a folder inside the Project that contains files related to one part of the overall work.
61
+ - The current Artifact is the default place where Max should do its work unless the user says otherwise.
62
+ </project_information>
63
+
64
+ <working_dir>
65
+ Max is currently working in the following project:
66
+ - project name: ${projectName}
67
+ - project dir: ${projectDir}
68
+
69
+ and the following artifact:
70
+ - artifact name: ${artifactName}
71
+ - artifact dir: ${artifactDir}
72
+
73
+ - The tools are initialized in the current artifact directory, and Max should treat this artifact as the default working area.
74
+ - If the user mentions files or directories from other artifacts, Max should explicitly read them.
75
+ - Max must not modify files in other artifacts unless the user explicitly asks for changes there.
76
+
77
+ <agent_state>
78
+ - Artifact-local agent state lives under: ${artifactMaxDir}
79
+ - Installed artifact skills live under: ${artifactSkillsDir}
80
+ - Max may use ${artifactTempDir} as a writable scratchpad for temporary files, helper projects, scripts, installs, previews, logs, JSON summaries, unpacked folders, and other ephemeral outputs.
81
+ - ${artifactTempDir} may already be initialized as a lightweight TypeScript workspace for helper-backed skills, including a \`package.json\`, \`tsconfig.json\`, and \`scripts/\` folder.
82
+ - When that temp workspace already exists, Max should inspect it and reuse it instead of setting up a new scratch project from zero.
83
+ - Max may place one-off TypeScript helper scripts inside ${artifactTempDir}/scripts and run them from ${artifactTempDir} when that workspace fits the task.
84
+ - If the user wants code or scripts to live in the artifact project itself, Max may write them in ${artifactDir} instead and import helper functions from \`@ank1015/llm-agents\` there.
85
+ - Final user-facing outputs should be written to ${artifactDir} unless the user explicitly asks for a different location.
86
+ - Max should treat ${artifactTempDir} as ephemeral scratch space. Other contents under ${artifactMaxDir} are agent state, not normal project files.
87
+ - Max should keep bundled skill files inside installed skill directories unchanged unless the user explicitly asks to modify them.
88
+ </agent_state>
89
+
90
+ Current date: ${dateTime}
91
+ </working_dir>
92
+ `;
93
+ return prompt;
94
+ }
95
+ async function formatInstalledSkills(artifactDir) {
96
+ const installedSkills = await listInstalledSkills(artifactDir);
97
+ if (installedSkills.length === 0) {
98
+ return '- none installed';
99
+ }
100
+ return installedSkills
101
+ .map((skill) => `- name: ${skill.name}
102
+ description: ${skill.description}
103
+ path: ${skill.path}`)
104
+ .join('\n');
105
+ }
106
+ //# sourceMappingURL=system-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/agents/system-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,EACvC,WAAW,EACX,UAAU,EACV,YAAY,EACZ,WAAW,GAMZ;IACC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACjD,MAAM,iBAAiB,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;IACzD,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE;QAC3C,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,MAAM;QACb,GAAG,EAAE,SAAS;KACf,CAAC,CAAC;IACH,MAAM,eAAe,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAEjE,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sJAmCqI,eAAe;;;EAGnK,eAAe;;;;;wDAKuC,WAAW,4BAA4B,YAAY;;;;;;;;kBAQzF,WAAW;iBACZ,UAAU;;;mBAGR,YAAY;kBACb,WAAW;;;;;;;4CAOe,cAAc;0CAChB,iBAAiB;gBAC3C,eAAe;IAC3B,eAAe;;2DAEwC,eAAe,8BAA8B,eAAe;oGACnB,WAAW;mDAC5D,WAAW;qBACzC,eAAe,qDAAqD,cAAc;;;;gBAIvF,QAAQ;;CAEvB,CAAC;IAEA,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,WAAmB;IACtD,MAAM,eAAe,GAAG,MAAM,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAE/D,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,OAAO,eAAe;SACnB,GAAG,CACF,CAAC,KAAK,EAAE,EAAE,CACR,WAAW,KAAK,CAAC,IAAI;iBACZ,KAAK,CAAC,WAAW;UACxB,KAAK,CAAC,IAAI,EAAE,CACjB;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { createAllTools } from '../tools/index.js';
2
+ export { createAllTools };
3
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/agents/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { createAllTools } from '../tools/index.js';
2
+ export { createAllTools };
3
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/agents/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,75 @@
1
+ export declare const IMAGE_MODEL_IDS: readonly ["gpt-5.4", "gemini-3.1-flash-image-preview", "gemini-3-pro-image-preview"];
2
+ export type ImageModelId = (typeof IMAGE_MODEL_IDS)[number];
3
+ export type ImageSource = string | readonly string[];
4
+ export declare const OPENAI_IMAGE_SIZES: readonly ["1024x1024", "1024x1536", "1536x1024", "auto"];
5
+ export declare const GOOGLE_IMAGE_SIZES: readonly ["1K", "2K", "4K"];
6
+ export declare const GOOGLE_PRO_IMAGE_ASPECT_RATIOS: readonly ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
7
+ export declare const GOOGLE_FLASH_IMAGE_ASPECT_RATIOS: readonly ["1:1", "1:4", "1:8", "2:3", "3:2", "3:4", "4:1", "4:3", "4:5", "5:4", "8:1", "9:16", "16:9", "21:9"];
8
+ export type OpenAIImageSize = (typeof OPENAI_IMAGE_SIZES)[number];
9
+ export type GoogleImageSize = (typeof GOOGLE_IMAGE_SIZES)[number];
10
+ export type GoogleProImageAspectRatio = (typeof GOOGLE_PRO_IMAGE_ASPECT_RATIOS)[number];
11
+ export type GoogleFlashImageAspectRatio = (typeof GOOGLE_FLASH_IMAGE_ASPECT_RATIOS)[number];
12
+ export interface OpenAIImageOptions {
13
+ format?: 'png' | 'jpeg' | 'webp';
14
+ quality?: 'low' | 'medium' | 'high' | 'auto';
15
+ background?: 'transparent' | 'opaque' | 'auto';
16
+ size?: OpenAIImageSize;
17
+ partialImages?: 0 | 1 | 2 | 3;
18
+ inputFidelity?: 'low' | 'high';
19
+ moderation?: 'auto' | 'low';
20
+ }
21
+ export interface GoogleProImageOptions {
22
+ aspectRatio?: GoogleProImageAspectRatio;
23
+ imageSize?: GoogleImageSize;
24
+ }
25
+ export interface GoogleFlashImageOptions {
26
+ aspectRatio?: GoogleFlashImageAspectRatio;
27
+ imageSize?: GoogleImageSize;
28
+ }
29
+ export type GoogleImageOptions = GoogleFlashImageOptions | GoogleProImageOptions;
30
+ export type ImageOptions = OpenAIImageOptions | GoogleImageOptions;
31
+ export interface OpenAIImageProvider {
32
+ model: 'gpt-5.4';
33
+ apiKey?: string;
34
+ imageOptions?: OpenAIImageOptions;
35
+ }
36
+ export interface GoogleFlashImageProvider {
37
+ model: 'gemini-3.1-flash-image-preview';
38
+ apiKey?: string;
39
+ imageOptions?: GoogleFlashImageOptions;
40
+ }
41
+ export interface GoogleProImageProvider {
42
+ model: 'gemini-3-pro-image-preview';
43
+ apiKey?: string;
44
+ imageOptions?: GoogleProImageOptions;
45
+ }
46
+ export type GoogleImageProvider = GoogleFlashImageProvider | GoogleProImageProvider;
47
+ export type ImageProvider = OpenAIImageProvider | GoogleImageProvider;
48
+ export interface ImageUpdate {
49
+ stage: 'partial' | 'thought' | 'final';
50
+ path: string;
51
+ mimeType: string;
52
+ index: number;
53
+ model: ImageModelId;
54
+ }
55
+ interface BaseImageRequest {
56
+ provider: ImageProvider;
57
+ prompt: string;
58
+ outputDir: string;
59
+ outputName?: string;
60
+ systemPrompt?: string;
61
+ onUpdate?: (update: ImageUpdate) => void | Promise<void>;
62
+ }
63
+ export interface CreateImageRequest extends BaseImageRequest {
64
+ images?: ImageSource;
65
+ }
66
+ export interface EditImageRequest extends BaseImageRequest {
67
+ images: ImageSource;
68
+ }
69
+ export interface ImageResult {
70
+ path: string;
71
+ }
72
+ export declare function createImage(request: CreateImageRequest): Promise<ImageResult>;
73
+ export declare function editImage(request: EditImageRequest): Promise<ImageResult>;
74
+ export {};
75
+ //# sourceMappingURL=ai-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-image.d.ts","sourceRoot":"","sources":["../../../src/helpers/ai-image/ai-image.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,438 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { basename, extname, join, parse, resolve } from 'node:path';
3
+ import { buildUserMessage, getModel, stream as sdkStream } from '@ank1015/llm-sdk';
4
+ import { createFileKeysAdapter } from '@ank1015/llm-sdk-adapters';
5
+ const defaultKeysAdapter = createFileKeysAdapter();
6
+ export const IMAGE_MODEL_IDS = [
7
+ 'gpt-5.4',
8
+ 'gemini-3.1-flash-image-preview',
9
+ 'gemini-3-pro-image-preview',
10
+ ];
11
+ export const OPENAI_IMAGE_SIZES = ['1024x1024', '1024x1536', '1536x1024', 'auto'];
12
+ export const GOOGLE_IMAGE_SIZES = ['1K', '2K', '4K'];
13
+ export const GOOGLE_PRO_IMAGE_ASPECT_RATIOS = [
14
+ '1:1',
15
+ '2:3',
16
+ '3:2',
17
+ '3:4',
18
+ '4:3',
19
+ '4:5',
20
+ '5:4',
21
+ '9:16',
22
+ '16:9',
23
+ '21:9',
24
+ ];
25
+ export const GOOGLE_FLASH_IMAGE_ASPECT_RATIOS = [
26
+ '1:1',
27
+ '1:4',
28
+ '1:8',
29
+ '2:3',
30
+ '3:2',
31
+ '3:4',
32
+ '4:1',
33
+ '4:3',
34
+ '4:5',
35
+ '5:4',
36
+ '8:1',
37
+ '9:16',
38
+ '16:9',
39
+ '21:9',
40
+ ];
41
+ const MIME_TYPE_TO_EXTENSION = {
42
+ 'image/gif': 'gif',
43
+ 'image/jpeg': 'jpg',
44
+ 'image/png': 'png',
45
+ 'image/webp': 'webp',
46
+ };
47
+ const EXTENSION_TO_MIME_TYPE = {
48
+ '.gif': 'image/gif',
49
+ '.jpeg': 'image/jpeg',
50
+ '.jpg': 'image/jpeg',
51
+ '.png': 'image/png',
52
+ '.webp': 'image/webp',
53
+ };
54
+ function isImageModelId(value) {
55
+ return IMAGE_MODEL_IDS.includes(value);
56
+ }
57
+ function assertImageModelId(model) {
58
+ if (!isImageModelId(model)) {
59
+ throw new Error(`Unsupported image model "${model}". Supported models: ${IMAGE_MODEL_IDS.join(', ')}.`);
60
+ }
61
+ }
62
+ function assertImageProvider(provider) {
63
+ if (typeof provider.model !== 'string') {
64
+ throw new Error('provider.model is required.');
65
+ }
66
+ assertImageModelId(provider.model);
67
+ }
68
+ function isHttpUrl(value) {
69
+ try {
70
+ const url = new URL(value);
71
+ return url.protocol === 'http:' || url.protocol === 'https:';
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ function inferMimeTypeFromExtension(pathLike) {
78
+ return EXTENSION_TO_MIME_TYPE[extname(pathLike).toLowerCase()];
79
+ }
80
+ function inferMimeTypeFromBuffer(buffer) {
81
+ if (buffer.length >= 8 &&
82
+ buffer[0] === 0x89 &&
83
+ buffer[1] === 0x50 &&
84
+ buffer[2] === 0x4e &&
85
+ buffer[3] === 0x47 &&
86
+ buffer[4] === 0x0d &&
87
+ buffer[5] === 0x0a &&
88
+ buffer[6] === 0x1a &&
89
+ buffer[7] === 0x0a) {
90
+ return 'image/png';
91
+ }
92
+ if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
93
+ return 'image/jpeg';
94
+ }
95
+ if (buffer.length >= 12 &&
96
+ buffer[0] === 0x52 &&
97
+ buffer[1] === 0x49 &&
98
+ buffer[2] === 0x46 &&
99
+ buffer[3] === 0x46 &&
100
+ buffer[8] === 0x57 &&
101
+ buffer[9] === 0x45 &&
102
+ buffer[10] === 0x42 &&
103
+ buffer[11] === 0x50) {
104
+ return 'image/webp';
105
+ }
106
+ if (buffer.length >= 6 &&
107
+ buffer[0] === 0x47 &&
108
+ buffer[1] === 0x49 &&
109
+ buffer[2] === 0x46 &&
110
+ buffer[3] === 0x38 &&
111
+ (buffer[4] === 0x37 || buffer[4] === 0x39) &&
112
+ buffer[5] === 0x61) {
113
+ return 'image/gif';
114
+ }
115
+ return undefined;
116
+ }
117
+ function inferMimeTypeFromContentType(contentType) {
118
+ if (!contentType)
119
+ return undefined;
120
+ const mimeType = contentType.split(';')[0]?.trim().toLowerCase();
121
+ if (!mimeType)
122
+ return undefined;
123
+ if (!mimeType.startsWith('image/'))
124
+ return undefined;
125
+ return MIME_TYPE_TO_EXTENSION[mimeType] ? mimeType : undefined;
126
+ }
127
+ function getExtensionForMimeType(mimeType) {
128
+ const extension = MIME_TYPE_TO_EXTENSION[mimeType];
129
+ if (!extension) {
130
+ throw new Error(`Unsupported image mime type "${mimeType}".`);
131
+ }
132
+ return extension;
133
+ }
134
+ function normalizeImageSources(images) {
135
+ if (images === undefined) {
136
+ return [];
137
+ }
138
+ return typeof images === 'string' ? [images] : [...images];
139
+ }
140
+ function sanitizeOutputStem(outputName) {
141
+ if (!outputName) {
142
+ return `image-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
143
+ }
144
+ const trimmed = outputName.trim();
145
+ if (!trimmed) {
146
+ throw new Error('outputName must not be empty.');
147
+ }
148
+ if (trimmed.includes('/') || trimmed.includes('\\') || trimmed === '.' || trimmed === '..') {
149
+ throw new Error('outputName must be a filename stem, not a path.');
150
+ }
151
+ const stem = parse(trimmed).name.trim();
152
+ if (!stem) {
153
+ throw new Error('outputName must contain at least one non-extension character.');
154
+ }
155
+ return stem;
156
+ }
157
+ async function loadLocalImageSource(source) {
158
+ const buffer = await readFile(source);
159
+ const mimeType = inferMimeTypeFromBuffer(buffer) ?? inferMimeTypeFromExtension(source);
160
+ if (!mimeType) {
161
+ throw new Error(`Could not determine image type for local source "${source}".`);
162
+ }
163
+ return {
164
+ fileName: basename(source),
165
+ mimeType,
166
+ data: buffer.toString('base64'),
167
+ size: buffer.length,
168
+ };
169
+ }
170
+ async function loadRemoteImageSource(source, index) {
171
+ const response = await fetch(source);
172
+ if (!response.ok) {
173
+ throw new Error(`Failed to download image source "${source}": ${response.status} ${response.statusText}`);
174
+ }
175
+ const arrayBuffer = await response.arrayBuffer();
176
+ const buffer = Buffer.from(arrayBuffer);
177
+ const url = new URL(source);
178
+ const mimeType = inferMimeTypeFromContentType(response.headers.get('content-type')) ??
179
+ inferMimeTypeFromBuffer(buffer) ??
180
+ inferMimeTypeFromExtension(url.pathname);
181
+ if (!mimeType) {
182
+ throw new Error(`Could not determine image type for remote source "${source}".`);
183
+ }
184
+ const fallbackFileName = `source-image-${index}.${getExtensionForMimeType(mimeType)}`;
185
+ const nameFromUrl = basename(url.pathname);
186
+ return {
187
+ fileName: nameFromUrl || fallbackFileName,
188
+ mimeType,
189
+ data: buffer.toString('base64'),
190
+ size: buffer.length,
191
+ };
192
+ }
193
+ async function loadImageSource(source, index) {
194
+ return isHttpUrl(source) ? loadRemoteImageSource(source, index) : loadLocalImageSource(source);
195
+ }
196
+ function resolveImageModel(provider) {
197
+ if (provider.model === 'gpt-5.4') {
198
+ const model = getModel('openai', provider.model);
199
+ if (!model) {
200
+ throw new Error(`Could not resolve model "${provider.model}".`);
201
+ }
202
+ return model;
203
+ }
204
+ const model = getModel('google', provider.model);
205
+ if (!model) {
206
+ throw new Error(`Could not resolve model "${provider.model}".`);
207
+ }
208
+ return model;
209
+ }
210
+ function validateImageOptions(method, provider) {
211
+ if (provider.model === 'gpt-5.4') {
212
+ const imageOptions = provider.imageOptions;
213
+ if (!imageOptions)
214
+ return;
215
+ const openAIImageOptions = imageOptions;
216
+ if ('aspectRatio' in openAIImageOptions) {
217
+ throw new Error('provider.imageOptions.aspectRatio is only supported for Google image models.');
218
+ }
219
+ if ('imageSize' in openAIImageOptions) {
220
+ throw new Error('provider.imageOptions.imageSize is only supported for Google image models.');
221
+ }
222
+ if (imageOptions.size !== undefined && !OPENAI_IMAGE_SIZES.includes(imageOptions.size)) {
223
+ throw new Error(`provider.imageOptions.size must be one of: ${OPENAI_IMAGE_SIZES.join(', ')}.`);
224
+ }
225
+ if (method === 'create' && imageOptions.inputFidelity !== undefined) {
226
+ throw new Error('provider.imageOptions.inputFidelity is only supported for editImage() with OpenAI.');
227
+ }
228
+ return;
229
+ }
230
+ const imageOptions = provider.imageOptions;
231
+ if (!imageOptions)
232
+ return;
233
+ const googleImageOptions = imageOptions;
234
+ for (const openAIOnlyKey of [
235
+ 'format',
236
+ 'quality',
237
+ 'background',
238
+ 'size',
239
+ 'partialImages',
240
+ 'inputFidelity',
241
+ 'moderation',
242
+ ]) {
243
+ if (openAIOnlyKey in googleImageOptions) {
244
+ throw new Error(`provider.imageOptions.${openAIOnlyKey} is only supported for OpenAI image models.`);
245
+ }
246
+ }
247
+ if (imageOptions.imageSize !== undefined &&
248
+ !GOOGLE_IMAGE_SIZES.includes(imageOptions.imageSize)) {
249
+ throw new Error(`provider.imageOptions.imageSize must be one of: ${GOOGLE_IMAGE_SIZES.join(', ')}.`);
250
+ }
251
+ const allowedAspectRatios = provider.model === 'gemini-3.1-flash-image-preview'
252
+ ? GOOGLE_FLASH_IMAGE_ASPECT_RATIOS
253
+ : GOOGLE_PRO_IMAGE_ASPECT_RATIOS;
254
+ if (imageOptions.aspectRatio !== undefined &&
255
+ !allowedAspectRatios.includes(imageOptions.aspectRatio)) {
256
+ throw new Error(`provider.imageOptions.aspectRatio must be one of: ${allowedAspectRatios.join(', ')}.`);
257
+ }
258
+ }
259
+ function buildOpenAIProviderOptions(method, provider, signal) {
260
+ const imageOptions = provider.imageOptions;
261
+ const tool = {
262
+ type: 'image_generation',
263
+ action: method === 'edit' ? 'edit' : 'generate',
264
+ };
265
+ if (imageOptions?.partialImages !== undefined) {
266
+ tool.partial_images = imageOptions.partialImages;
267
+ }
268
+ if (imageOptions?.inputFidelity !== undefined) {
269
+ tool.input_fidelity = imageOptions.inputFidelity;
270
+ }
271
+ if (imageOptions?.size !== undefined) {
272
+ tool.size = imageOptions.size;
273
+ }
274
+ if (imageOptions?.quality !== undefined) {
275
+ tool.quality = imageOptions.quality;
276
+ }
277
+ if (imageOptions?.background !== undefined) {
278
+ tool.background = imageOptions.background;
279
+ }
280
+ if (imageOptions?.format !== undefined) {
281
+ tool.output_format = imageOptions.format;
282
+ }
283
+ if (imageOptions?.moderation !== undefined) {
284
+ tool.moderation = imageOptions.moderation;
285
+ }
286
+ return {
287
+ ...(provider.apiKey ? { apiKey: provider.apiKey } : {}),
288
+ signal,
289
+ tools: [tool],
290
+ };
291
+ }
292
+ function buildGoogleProviderOptions(provider, signal) {
293
+ const imageConfig = {};
294
+ if (provider.imageOptions?.aspectRatio !== undefined) {
295
+ imageConfig.aspectRatio = provider.imageOptions.aspectRatio;
296
+ }
297
+ if (provider.imageOptions?.imageSize !== undefined) {
298
+ imageConfig.imageSize = provider.imageOptions.imageSize;
299
+ }
300
+ return {
301
+ ...(provider.apiKey ? { apiKey: provider.apiKey } : {}),
302
+ signal,
303
+ responseModalities: ['IMAGE'],
304
+ ...(Object.keys(imageConfig).length > 0 ? { imageConfig } : {}),
305
+ };
306
+ }
307
+ function getFirstFinalImage(message) {
308
+ for (const block of message.content) {
309
+ if (block.type !== 'response')
310
+ continue;
311
+ for (const content of block.content) {
312
+ if (content.type === 'image' && content.metadata?.generationStage === 'final') {
313
+ return content;
314
+ }
315
+ }
316
+ }
317
+ return undefined;
318
+ }
319
+ async function writeImage(filePath, image) {
320
+ await writeFile(filePath, Buffer.from(image.data, 'base64'));
321
+ }
322
+ function getEventImageStage(event) {
323
+ if (event.type === 'image_start') {
324
+ return event.metadata?.generationStage;
325
+ }
326
+ if (event.type === 'image_frame' || event.type === 'image_end') {
327
+ const stage = event.image.metadata?.generationStage;
328
+ return stage === 'partial' || stage === 'thought' || stage === 'final' ? stage : undefined;
329
+ }
330
+ return undefined;
331
+ }
332
+ async function runImageRequest(method, request) {
333
+ assertImageProvider(request.provider);
334
+ validateImageOptions(method, request.provider);
335
+ const imageSources = normalizeImageSources(request.images);
336
+ if (method === 'edit' && imageSources.length === 0) {
337
+ throw new Error('editImage() requires at least one image source.');
338
+ }
339
+ const outputDir = resolve(request.outputDir);
340
+ const baseName = sanitizeOutputStem(request.outputName);
341
+ const updatesDir = join(outputDir, `${baseName}__updates`);
342
+ await mkdir(outputDir, { recursive: true });
343
+ await mkdir(updatesDir, { recursive: true });
344
+ const loadedImages = await Promise.all(imageSources.map((source, index) => loadImageSource(source, index + 1)));
345
+ const attachments = loadedImages.map((image, index) => ({
346
+ id: `source-image-${index + 1}`,
347
+ type: 'image',
348
+ fileName: image.fileName,
349
+ mimeType: image.mimeType,
350
+ size: image.size,
351
+ content: image.data,
352
+ }));
353
+ const userMessage = buildUserMessage(request.prompt, attachments);
354
+ const model = resolveImageModel(request.provider);
355
+ const abortController = new AbortController();
356
+ const counters = {
357
+ partial: 0,
358
+ thought: 0,
359
+ final: 0,
360
+ };
361
+ let firstFinalUpdateSaved = false;
362
+ let firstFinalImage;
363
+ const saveUpdate = async (stage, image) => {
364
+ if (stage === 'final') {
365
+ if (firstFinalUpdateSaved) {
366
+ return undefined;
367
+ }
368
+ firstFinalUpdateSaved = true;
369
+ firstFinalImage = image;
370
+ }
371
+ const index = ++counters[stage];
372
+ const extension = getExtensionForMimeType(image.mimeType);
373
+ const artifactPath = join(updatesDir, `${stage}-${String(index).padStart(3, '0')}.${extension}`);
374
+ await writeImage(artifactPath, image);
375
+ if (request.onUpdate) {
376
+ await request.onUpdate({
377
+ stage,
378
+ path: artifactPath,
379
+ mimeType: image.mimeType,
380
+ index,
381
+ model: request.provider.model,
382
+ });
383
+ }
384
+ return artifactPath;
385
+ };
386
+ try {
387
+ const providerOptions = request.provider.model === 'gpt-5.4'
388
+ ? buildOpenAIProviderOptions(method, request.provider, abortController.signal)
389
+ : buildGoogleProviderOptions(request.provider, abortController.signal);
390
+ const eventStream = await sdkStream(model, {
391
+ messages: [userMessage],
392
+ ...(request.systemPrompt ? { systemPrompt: request.systemPrompt } : {}),
393
+ }, {
394
+ keysAdapter: defaultKeysAdapter,
395
+ providerOptions,
396
+ });
397
+ for await (const event of eventStream) {
398
+ const stage = getEventImageStage(event);
399
+ if (!stage)
400
+ continue;
401
+ if (stage === 'partial' && event.type === 'image_frame') {
402
+ await saveUpdate(stage, event.image);
403
+ }
404
+ if ((stage === 'thought' || stage === 'final') && event.type === 'image_end') {
405
+ await saveUpdate(stage, event.image);
406
+ }
407
+ }
408
+ const message = await eventStream.result();
409
+ if (message.stopReason === 'error' || message.stopReason === 'aborted') {
410
+ throw new Error(message.errorMessage ||
411
+ (message.stopReason === 'aborted'
412
+ ? 'Image request was aborted.'
413
+ : 'Image request failed.'));
414
+ }
415
+ const finalImage = firstFinalImage ?? getFirstFinalImage(message);
416
+ if (!finalImage) {
417
+ throw new Error(`Model "${request.provider.model}" did not return a final image.`);
418
+ }
419
+ if (!firstFinalUpdateSaved) {
420
+ await saveUpdate('final', finalImage);
421
+ }
422
+ const extension = getExtensionForMimeType(finalImage.mimeType);
423
+ const finalPath = join(outputDir, `${baseName}.${extension}`);
424
+ await writeImage(finalPath, finalImage);
425
+ return { path: finalPath };
426
+ }
427
+ catch (error) {
428
+ abortController.abort();
429
+ throw error;
430
+ }
431
+ }
432
+ export async function createImage(request) {
433
+ return runImageRequest('create', request);
434
+ }
435
+ export async function editImage(request) {
436
+ return runImageRequest('edit', request);
437
+ }
438
+ //# sourceMappingURL=ai-image.js.map