runline 0.7.6 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugins/_shared/imageFile.js +40 -0
- package/dist/plugins/_shared/microsoftAuth.js +100 -0
- package/dist/plugins/gmail/src/index.js +13 -1
- package/dist/plugins/googleAppsScript/src/index.js +203 -0
- package/dist/plugins/googleCalendar/src/index.js +167 -0
- package/dist/plugins/googleDocs/src/index.js +420 -0
- package/dist/plugins/googleDrive/src/index.js +669 -0
- package/dist/plugins/googleImage/src/index.js +30 -11
- package/dist/plugins/googleSheets/src/index.js +275 -0
- package/dist/plugins/microsoftCalendar/src/index.js +46 -0
- package/dist/plugins/microsoftFiles/src/index.js +127 -0
- package/dist/plugins/microsoftMail/src/index.js +91 -0
- package/dist/plugins/openai/src/index.js +45 -20
- package/dist/plugins/parallel/src/index.js +100 -0
- package/dist/plugins/recraft/src/index.js +10 -6
- package/dist/plugins/replicate/src/index.js +17 -3
- package/dist/plugins/steel/src/index.js +378 -0
- package/dist/plugins/together/src/index.js +10 -6
- package/dist/plugins/xai/src/index.js +11 -5
- package/package.json +1 -1
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenAI image generation for runline.
|
|
3
3
|
*
|
|
4
|
-
* Wraps the GPT Image / DALL-E line at /v1/images/generations
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Wraps the GPT Image / DALL-E line at /v1/images/generations. Generated
|
|
5
|
+
* images are written to disk and the action returns their file `path`s
|
|
6
|
+
* (plus the optional revised prompt) — never raw base64, which bloats the
|
|
7
|
+
* agent context and is stripped before delivery. Hand each `path` to the
|
|
8
|
+
* host's file-sending tool (e.g. send_file) to deliver the image.
|
|
7
9
|
*
|
|
8
10
|
* Quality leader for text rendering and prompt adherence — pair with
|
|
9
11
|
* any other plugin you'd compose images for (storyblok, github,
|
|
10
12
|
* notion, slack uploads, …).
|
|
11
13
|
*
|
|
12
|
-
* await openai.image.create({ prompt: "a red bicycle on snow" })
|
|
13
|
-
*
|
|
14
|
-
* prompt: "logo for a coffee shop",
|
|
15
|
-
* model: "dall-e-3",
|
|
16
|
-
* style: "vivid",
|
|
17
|
-
* quality: "high",
|
|
18
|
-
* size: "1024x1024",
|
|
19
|
-
* })
|
|
14
|
+
* const { images } = await openai.image.create({ prompt: "a red bicycle on snow" })
|
|
15
|
+
* // images[0].path -> "/tmp/openai-image-….png"
|
|
20
16
|
*/
|
|
17
|
+
import { writeFileSync } from "node:fs";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
21
20
|
const ENDPOINT = "https://api.openai.com/v1/images/generations";
|
|
22
21
|
export default function openai(rl) {
|
|
23
22
|
rl.setName("openai");
|
|
@@ -29,19 +28,30 @@ export default function openai(rl) {
|
|
|
29
28
|
description: "OpenAI API key",
|
|
30
29
|
env: "OPENAI_API_KEY",
|
|
31
30
|
},
|
|
31
|
+
defaultModel: {
|
|
32
|
+
type: "string",
|
|
33
|
+
required: false,
|
|
34
|
+
description: "Default image model when a call omits `model` (e.g. gpt-image-2). Falls back to gpt-image-1.",
|
|
35
|
+
env: "OPENAI_IMAGE_MODEL",
|
|
36
|
+
},
|
|
32
37
|
});
|
|
33
38
|
rl.registerAction("image.create", {
|
|
34
|
-
description: "Generate an image with OpenAI (GPT Image / DALL-E).
|
|
39
|
+
description: "Generate an image with OpenAI (GPT Image / DALL-E). Writes the PNG(s) to disk and returns their file `path`s (plus any revised prompt) — not base64. Deliver each image to the user with send_file using its `path`.",
|
|
35
40
|
inputSchema: {
|
|
36
41
|
prompt: {
|
|
37
42
|
type: "string",
|
|
38
43
|
required: true,
|
|
39
44
|
description: "Detailed description of the image",
|
|
40
45
|
},
|
|
46
|
+
saveDir: {
|
|
47
|
+
type: "string",
|
|
48
|
+
required: false,
|
|
49
|
+
description: "Directory to write the image file(s) into. Defaults to the OS temp dir.",
|
|
50
|
+
},
|
|
41
51
|
model: {
|
|
42
52
|
type: "string",
|
|
43
53
|
required: false,
|
|
44
|
-
description: "gpt-image-1
|
|
54
|
+
description: "gpt-image-2 | gpt-image-1 | gpt-image-1-mini | dall-e-3 | dall-e-2. Omit to use the connection default.",
|
|
45
55
|
},
|
|
46
56
|
size: {
|
|
47
57
|
type: "string",
|
|
@@ -70,7 +80,9 @@ export default function openai(rl) {
|
|
|
70
80
|
throw new Error("openai: prompt is required");
|
|
71
81
|
}
|
|
72
82
|
const apiKey = ctx.connection.config.apiKey;
|
|
73
|
-
const model = p.model ??
|
|
83
|
+
const model = p.model ??
|
|
84
|
+
ctx.connection.config.defaultModel ??
|
|
85
|
+
"gpt-image-1";
|
|
74
86
|
const body = {
|
|
75
87
|
model,
|
|
76
88
|
prompt: p.prompt,
|
|
@@ -101,12 +113,25 @@ export default function openai(rl) {
|
|
|
101
113
|
throw new Error(`OpenAI API error ${res.status}: ${await res.text()}`);
|
|
102
114
|
}
|
|
103
115
|
const data = (await res.json());
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
const dir = (typeof p.saveDir === "string" && p.saveDir.trim()) || tmpdir();
|
|
117
|
+
const stamp = Date.now();
|
|
118
|
+
const images = (data.data ?? []).map((d, i) => {
|
|
119
|
+
const bytes = Buffer.from(d.b64_json ?? "", "base64");
|
|
120
|
+
const path = join(dir, `openai-image-${stamp}-${i}.png`);
|
|
121
|
+
writeFileSync(path, bytes);
|
|
122
|
+
return {
|
|
123
|
+
path,
|
|
124
|
+
mimeType: "image/png",
|
|
125
|
+
byteLength: bytes.length,
|
|
126
|
+
...(d.revised_prompt ? { revisedPrompt: d.revised_prompt } : {}),
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
provider: "openai",
|
|
131
|
+
model,
|
|
132
|
+
images,
|
|
133
|
+
note: "Image(s) written to disk. Deliver each to the user with send_file using its `path`.",
|
|
134
|
+
};
|
|
110
135
|
},
|
|
111
136
|
});
|
|
112
137
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const NAME = "parallel";
|
|
2
|
+
const DEFAULT_BASE = "https://api.parallel.ai";
|
|
3
|
+
export default function parallel(rl) {
|
|
4
|
+
rl.setName(NAME);
|
|
5
|
+
rl.setVersion("0.1.0");
|
|
6
|
+
rl.setConnectionSchema({
|
|
7
|
+
apiKey: {
|
|
8
|
+
type: "string",
|
|
9
|
+
required: true,
|
|
10
|
+
env: "PARALLEL_API_KEY",
|
|
11
|
+
description: "Parallel.ai API key (sent as the x-api-key header). Store only in secrets.",
|
|
12
|
+
},
|
|
13
|
+
baseUrl: {
|
|
14
|
+
type: "string",
|
|
15
|
+
required: false,
|
|
16
|
+
env: "PARALLEL_API_BASE",
|
|
17
|
+
default: DEFAULT_BASE,
|
|
18
|
+
description: "Parallel.ai API base URL.",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
rl.registerAction("search", {
|
|
22
|
+
description: "Search the live web with Parallel.ai and get ranked results with extracted excerpts. Use this to ground answers in current web content (news, prices, regulations, company info) instead of stale knowledge. Provide an `objective` (natural-language goal) and/or explicit `search_queries`.",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
objective: {
|
|
25
|
+
type: "string",
|
|
26
|
+
required: false,
|
|
27
|
+
description: "Natural-language description of what you're trying to find. Recommended; can be used with or instead of search_queries.",
|
|
28
|
+
},
|
|
29
|
+
search_queries: {
|
|
30
|
+
type: "array",
|
|
31
|
+
required: false,
|
|
32
|
+
description: "Optional explicit query strings to run (e.g. [\"x vacancy rate 2026\"]). Provide objective and/or this.",
|
|
33
|
+
},
|
|
34
|
+
processor: {
|
|
35
|
+
type: "string",
|
|
36
|
+
required: false,
|
|
37
|
+
default: "base",
|
|
38
|
+
description: "base (fast, default) or pro (deeper, slower/costlier).",
|
|
39
|
+
},
|
|
40
|
+
max_results: {
|
|
41
|
+
type: "number",
|
|
42
|
+
required: false,
|
|
43
|
+
default: 5,
|
|
44
|
+
description: "Max results to return (default 5).",
|
|
45
|
+
},
|
|
46
|
+
max_chars_per_result: {
|
|
47
|
+
type: "number",
|
|
48
|
+
required: false,
|
|
49
|
+
description: "Optional cap on extracted characters per result.",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
async execute(input, ctx) {
|
|
53
|
+
const cfg = (ctx.connection.config ?? {});
|
|
54
|
+
const apiKey = cfg.apiKey;
|
|
55
|
+
if (!apiKey)
|
|
56
|
+
throw new Error("Missing PARALLEL_API_KEY. Configure it before using the parallel plugin.");
|
|
57
|
+
const baseUrl = (cfg.baseUrl || DEFAULT_BASE).replace(/\/+$/, "");
|
|
58
|
+
const objective = typeof input.objective === "string" ? input.objective.trim() : "";
|
|
59
|
+
const queries = Array.isArray(input.search_queries)
|
|
60
|
+
? input.search_queries.map((q) => String(q)).filter((q) => q.trim())
|
|
61
|
+
: [];
|
|
62
|
+
if (!objective && !queries.length) {
|
|
63
|
+
throw new Error("Provide objective and/or search_queries");
|
|
64
|
+
}
|
|
65
|
+
const body = {
|
|
66
|
+
processor: String(input.processor || "base"),
|
|
67
|
+
max_results: Number(input.max_results) > 0 ? Math.floor(Number(input.max_results)) : 5,
|
|
68
|
+
};
|
|
69
|
+
if (objective)
|
|
70
|
+
body.objective = objective;
|
|
71
|
+
if (queries.length)
|
|
72
|
+
body.search_queries = queries;
|
|
73
|
+
if (Number(input.max_chars_per_result) > 0) {
|
|
74
|
+
body.max_chars_per_result = Math.floor(Number(input.max_chars_per_result));
|
|
75
|
+
}
|
|
76
|
+
const res = await fetch(`${baseUrl}/v1beta/search`, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify(body),
|
|
80
|
+
});
|
|
81
|
+
const text = await res.text();
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
throw new Error(`Parallel search -> ${res.status}: ${text.slice(0, 300)}`);
|
|
84
|
+
}
|
|
85
|
+
const data = text ? JSON.parse(text) : {};
|
|
86
|
+
const results = (data.results ?? []).map((r) => ({
|
|
87
|
+
url: r.url,
|
|
88
|
+
title: r.title,
|
|
89
|
+
excerpts: r.excerpts ?? [],
|
|
90
|
+
}));
|
|
91
|
+
return {
|
|
92
|
+
searchId: data.search_id,
|
|
93
|
+
count: results.length,
|
|
94
|
+
results,
|
|
95
|
+
warnings: data.warnings ?? undefined,
|
|
96
|
+
source: "parallel.search",
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* legacy `style` knob — pass `styleId` against your own custom
|
|
14
14
|
* style if you've set one up.
|
|
15
15
|
*/
|
|
16
|
+
import { SEND_FILE_NOTE, writeImageFile } from "../../_shared/imageFile.js";
|
|
16
17
|
const ENDPOINT = "https://external.api.recraft.ai/v1/images/generations";
|
|
17
18
|
export default function recraft(rl) {
|
|
18
19
|
rl.setName("recraft");
|
|
@@ -26,13 +27,18 @@ export default function recraft(rl) {
|
|
|
26
27
|
},
|
|
27
28
|
});
|
|
28
29
|
rl.registerAction("image.create", {
|
|
29
|
-
description: "Generate an image with Recraft. Best for design, vector graphics, and brand-consistent work.
|
|
30
|
+
description: "Generate an image with Recraft. Best for design, vector graphics, and brand-consistent work. Writes the image(s) to disk and returns their file `path`s — not base64. Deliver each with send_file using its `path`.",
|
|
30
31
|
inputSchema: {
|
|
31
32
|
prompt: {
|
|
32
33
|
type: "string",
|
|
33
34
|
required: true,
|
|
34
35
|
description: "Detailed description of the image",
|
|
35
36
|
},
|
|
37
|
+
saveDir: {
|
|
38
|
+
type: "string",
|
|
39
|
+
required: false,
|
|
40
|
+
description: "Directory to write the image file(s) into. Defaults to the OS temp dir.",
|
|
41
|
+
},
|
|
36
42
|
model: {
|
|
37
43
|
type: "string",
|
|
38
44
|
required: false,
|
|
@@ -90,11 +96,9 @@ export default function recraft(rl) {
|
|
|
90
96
|
throw new Error(`Recraft API error ${res.status}: ${await res.text()}`);
|
|
91
97
|
}
|
|
92
98
|
const data = (await res.json());
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}));
|
|
97
|
-
return { provider: "recraft", model, images };
|
|
99
|
+
const stamp = Date.now();
|
|
100
|
+
const images = (data.data ?? []).map((d, i) => writeImageFile({ base64: d.b64_json, mimeType: "image/png", provider: "recraft", index: i, saveDir: p.saveDir, stamp }));
|
|
101
|
+
return { provider: "recraft", model, images, note: SEND_FILE_NOTE };
|
|
98
102
|
},
|
|
99
103
|
});
|
|
100
104
|
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* separately.
|
|
20
20
|
*/
|
|
21
21
|
import { Buffer } from "node:buffer";
|
|
22
|
+
import { SEND_FILE_NOTE, writeImageFile } from "../../_shared/imageFile.js";
|
|
22
23
|
import { parseSize } from "../../_shared/parseSize.js";
|
|
23
24
|
const POLL_INTERVAL_MS = 2_000;
|
|
24
25
|
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
@@ -46,13 +47,18 @@ export default function replicate(rl) {
|
|
|
46
47
|
},
|
|
47
48
|
});
|
|
48
49
|
rl.registerAction("image.create", {
|
|
49
|
-
description: "Generate an image via Replicate. Default model is black-forest-labs/flux-dev.
|
|
50
|
+
description: "Generate an image via Replicate. Default model is black-forest-labs/flux-dev. Writes the image(s) to disk and returns their file `path`s — not base64. Deliver each with send_file using its `path`.",
|
|
50
51
|
inputSchema: {
|
|
51
52
|
prompt: {
|
|
52
53
|
type: "string",
|
|
53
54
|
required: true,
|
|
54
55
|
description: "Detailed description of the image",
|
|
55
56
|
},
|
|
57
|
+
saveDir: {
|
|
58
|
+
type: "string",
|
|
59
|
+
required: false,
|
|
60
|
+
description: "Directory to write the image file(s) into. Defaults to the OS temp dir.",
|
|
61
|
+
},
|
|
56
62
|
model: {
|
|
57
63
|
type: "string",
|
|
58
64
|
required: false,
|
|
@@ -136,6 +142,7 @@ export default function replicate(rl) {
|
|
|
136
142
|
: [];
|
|
137
143
|
const images = [];
|
|
138
144
|
const failures = [];
|
|
145
|
+
const stamp = Date.now();
|
|
139
146
|
for (const url of outputs) {
|
|
140
147
|
if (typeof url !== "string") {
|
|
141
148
|
failures.push({ url: String(url), reason: "non-string output" });
|
|
@@ -153,13 +160,20 @@ export default function replicate(rl) {
|
|
|
153
160
|
const contentType = (imgRes.headers.get("content-type") ?? "image/webp")
|
|
154
161
|
.split(";")[0]
|
|
155
162
|
.trim();
|
|
156
|
-
images.push({
|
|
163
|
+
images.push(writeImageFile({
|
|
164
|
+
base64: buf.toString("base64"),
|
|
165
|
+
mimeType: contentType,
|
|
166
|
+
provider: "replicate",
|
|
167
|
+
index: images.length,
|
|
168
|
+
saveDir: p.saveDir,
|
|
169
|
+
stamp,
|
|
170
|
+
}));
|
|
157
171
|
}
|
|
158
172
|
if (images.length === 0 && outputs.length > 0) {
|
|
159
173
|
const detail = failures.map((f) => `${f.url}: ${f.reason}`).join("; ");
|
|
160
174
|
throw new Error(`Replicate succeeded but all ${outputs.length} output URLs failed to download — ${detail}`);
|
|
161
175
|
}
|
|
162
|
-
const result = { provider: "replicate", model, images };
|
|
176
|
+
const result = { provider: "replicate", model, images, note: SEND_FILE_NOTE };
|
|
163
177
|
if (failures.length > 0)
|
|
164
178
|
result.failures = failures;
|
|
165
179
|
return result;
|