teapot-coding-agent 0.19.9 → 0.20.1
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 +1 -1
- package/dist/agent/agent.js +68 -9
- package/dist/agent/llm.js +9 -1
- package/dist/model-meta.js +6 -0
- package/dist/server/api.js +18 -3
- package/package.json +1 -1
- package/public/assets/index-B2uMcMuc.js +30 -0
- package/public/assets/index-B2uMcMuc.js.map +1 -0
- package/public/assets/index-DzMlLjkj.css +1 -0
- package/public/assets/{shiki-D_5TubFo.js → shiki-ByPYlojK.js} +2 -2
- package/public/assets/{shiki-D_5TubFo.js.map → shiki-ByPYlojK.js.map} +1 -1
- package/public/index.html +2 -2
- package/public/assets/index-CtNqlaOx.css +0 -1
- package/public/assets/index-GHEvob3A.js +0 -30
- package/public/assets/index-GHEvob3A.js.map +0 -1
package/README.md
CHANGED
|
@@ -238,7 +238,7 @@ GET /api/agents/:id/file?path= fetch text file
|
|
|
238
238
|
PUT /api/agents/:id/file?path= save { content, baseContent? } (409 on conflict)
|
|
239
239
|
GET /api/agents/:id/raw?path= raw media bytes
|
|
240
240
|
GET /api/agents/:id/tree file tree
|
|
241
|
-
POST /api/agents/:id/prompt send { text, start
|
|
241
|
+
POST /api/agents/:id/prompt send { text, start?, images?: [{url}] } → { promptId }
|
|
242
242
|
POST /api/agents/:id/prompt/cancel withdraw { promptId } (409 once delivered)
|
|
243
243
|
POST /api/agents/:id/goal { text?, status?, verify? }
|
|
244
244
|
POST /api/agents/:id/start|stop|fork|load
|
package/dist/agent/agent.js
CHANGED
|
@@ -744,7 +744,7 @@ export class Agent {
|
|
|
744
744
|
* the running loop. The very first prompt on a fresh boot also triggers the
|
|
745
745
|
* lazy session restore (before the mailbox is filled, so no duplicates).
|
|
746
746
|
*/
|
|
747
|
-
enqueuePrompt(text, source = "user") {
|
|
747
|
+
enqueuePrompt(text, source = "user", images) {
|
|
748
748
|
// a prompt during a tool park (wait_children) takes over instantly:
|
|
749
749
|
// unpark fixes the DISPLAY, aborting the park signal actually ends the
|
|
750
750
|
// wait — the run chain resumes with this prompt drained at the boundary
|
|
@@ -770,10 +770,11 @@ export class Agent {
|
|
|
770
770
|
const promptId = source === "user" ? `u${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}` : "";
|
|
771
771
|
void this.ensureReady()
|
|
772
772
|
.then(() => {
|
|
773
|
-
this.pendingPrompts.push({ source, text, ...(promptId ? { id: promptId } : {}) });
|
|
773
|
+
this.pendingPrompts.push({ source, text, images, ...(promptId ? { id: promptId } : {}) });
|
|
774
774
|
return this.log.append("prompt", this.currentSession, this.currentBranch, {
|
|
775
775
|
source,
|
|
776
776
|
text,
|
|
777
|
+
...(images?.length ? { images: images.map((i) => ({ url: i.url, name: i.name })) } : {}),
|
|
777
778
|
...(promptId ? { promptId } : {}),
|
|
778
779
|
});
|
|
779
780
|
})
|
|
@@ -808,7 +809,21 @@ export class Agent {
|
|
|
808
809
|
*/
|
|
809
810
|
drainPendingPrompts() {
|
|
810
811
|
for (const p of this.pendingPrompts.splice(0)) {
|
|
811
|
-
|
|
812
|
+
// multimodal: images ride as OpenAI content parts (text first, then
|
|
813
|
+
// images); plain prompts keep the string form for cache friendliness
|
|
814
|
+
if (p.images?.length) {
|
|
815
|
+
this.messages.push({
|
|
816
|
+
role: "user",
|
|
817
|
+
content: p.text,
|
|
818
|
+
content_parts: [
|
|
819
|
+
{ type: "text", text: p.text },
|
|
820
|
+
...p.images.map((i) => ({ type: "image_url", image_url: { url: i.url } })),
|
|
821
|
+
],
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
this.messages.push({ role: "user", content: p.text });
|
|
826
|
+
}
|
|
812
827
|
if (p.source === "user") {
|
|
813
828
|
const id = p.id ?? `u${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
814
829
|
this.deliveredPrompts.push(id);
|
|
@@ -2109,19 +2124,48 @@ function rebuildMessagesFrom(list) {
|
|
|
2109
2124
|
// synthesizer below must not duplicate them
|
|
2110
2125
|
const loggedResults = new Set(list.filter((e) => e.type === "tool_result").map((e) => String(e.data.callId ?? "")));
|
|
2111
2126
|
const bufferedUsers = [];
|
|
2127
|
+
// image attachments paired with buffered prompts (multimodal replay)
|
|
2128
|
+
const bufferedParts = [];
|
|
2112
2129
|
const flushUsers = () => {
|
|
2113
2130
|
if (openCalls.size === 0) {
|
|
2114
|
-
|
|
2115
|
-
|
|
2131
|
+
const texts = bufferedUsers.splice(0);
|
|
2132
|
+
const parts = bufferedParts.splice(0);
|
|
2133
|
+
texts.forEach((text, i) => {
|
|
2134
|
+
const imgs = parts[i] ?? [];
|
|
2135
|
+
msgs.push(imgs.length
|
|
2136
|
+
? {
|
|
2137
|
+
role: "user",
|
|
2138
|
+
content: text,
|
|
2139
|
+
content_parts: [
|
|
2140
|
+
{ type: "text", text },
|
|
2141
|
+
...imgs.map((im) => ({ type: "image_url", image_url: { url: im.url } })),
|
|
2142
|
+
],
|
|
2143
|
+
}
|
|
2144
|
+
: { role: "user", content: text });
|
|
2145
|
+
});
|
|
2116
2146
|
}
|
|
2117
2147
|
};
|
|
2118
2148
|
for (const e of list) {
|
|
2119
2149
|
const d = e.data;
|
|
2120
2150
|
if (e.type === "prompt" && typeof d.text === "string") {
|
|
2151
|
+
// restore image attachments so replays stay byte-identical with the
|
|
2152
|
+
// original request (prefix caches stay warm across restarts)
|
|
2153
|
+
const text = d.text;
|
|
2154
|
+
const imgs = Array.isArray(d.images) ? d.images : [];
|
|
2155
|
+
const mk = () => imgs.length
|
|
2156
|
+
? {
|
|
2157
|
+
role: "user",
|
|
2158
|
+
content: text,
|
|
2159
|
+
content_parts: [
|
|
2160
|
+
{ type: "text", text },
|
|
2161
|
+
...imgs.map((i) => ({ type: "image_url", image_url: { url: i.url } })),
|
|
2162
|
+
],
|
|
2163
|
+
}
|
|
2164
|
+
: { role: "user", content: text };
|
|
2121
2165
|
if (openCalls.size > 0)
|
|
2122
|
-
bufferedUsers.push(
|
|
2166
|
+
bufferedUsers.push(text), bufferedParts.push(imgs);
|
|
2123
2167
|
else
|
|
2124
|
-
msgs.push(
|
|
2168
|
+
msgs.push(mk());
|
|
2125
2169
|
}
|
|
2126
2170
|
else if (e.type === "message") {
|
|
2127
2171
|
// final summaries are operator-facing only — the live loop never puts
|
|
@@ -2229,7 +2273,22 @@ function rebuildMessagesFrom(list) {
|
|
|
2229
2273
|
}
|
|
2230
2274
|
}
|
|
2231
2275
|
// prompts that were still waiting on a hole-filled tail land here
|
|
2232
|
-
|
|
2233
|
-
|
|
2276
|
+
{
|
|
2277
|
+
const texts = bufferedUsers.splice(0);
|
|
2278
|
+
const parts = bufferedParts.splice(0);
|
|
2279
|
+
texts.forEach((text, i) => {
|
|
2280
|
+
const imgs = parts[i] ?? [];
|
|
2281
|
+
msgs.push(imgs.length
|
|
2282
|
+
? {
|
|
2283
|
+
role: "user",
|
|
2284
|
+
content: text,
|
|
2285
|
+
content_parts: [
|
|
2286
|
+
{ type: "text", text },
|
|
2287
|
+
...imgs.map((im) => ({ type: "image_url", image_url: { url: im.url } })),
|
|
2288
|
+
],
|
|
2289
|
+
}
|
|
2290
|
+
: { role: "user", content: text });
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2234
2293
|
return msgs;
|
|
2235
2294
|
}
|
package/dist/agent/llm.js
CHANGED
|
@@ -80,13 +80,21 @@ function looksMangled(name) {
|
|
|
80
80
|
}
|
|
81
81
|
function sanitize(messages) {
|
|
82
82
|
return messages.map((m) => {
|
|
83
|
+
// multimodal user message → OpenAI content-parts array (text + images);
|
|
84
|
+
// providers without vision reject it, but those models were never a fit
|
|
85
|
+
if (m.content_parts?.length) {
|
|
86
|
+
const parts = m.content_parts.map((p) => p.type === "image_url"
|
|
87
|
+
? { type: "image_url", image_url: { url: p.image_url.url } }
|
|
88
|
+
: { type: "text", text: p.text ?? "" });
|
|
89
|
+
return { role: m.role, content: parts };
|
|
90
|
+
}
|
|
83
91
|
if ((m.role === "user" || m.role === "assistant") && !m.content) {
|
|
84
92
|
return { ...m, content: m.tool_calls?.length ? "(tool call)" : "(no content)" };
|
|
85
93
|
}
|
|
86
94
|
if (m.role === "tool" && typeof m.content !== "string") {
|
|
87
95
|
return { ...m, content: String(m.content ?? "(no output)") };
|
|
88
96
|
}
|
|
89
|
-
return m;
|
|
97
|
+
return { ...m };
|
|
90
98
|
});
|
|
91
99
|
}
|
|
92
100
|
export async function chat(cfg, messages, tools, signal, onDelta) {
|
package/dist/model-meta.js
CHANGED
|
@@ -38,6 +38,12 @@ export async function fetchModelList(baseUrl, apiKey) {
|
|
|
38
38
|
completion: Number(m.pricing.completion ?? 0),
|
|
39
39
|
}
|
|
40
40
|
: undefined,
|
|
41
|
+
modalities: Array.isArray(m.architecture?.input_modalities) && m.architecture.input_modalities.length
|
|
42
|
+
? {
|
|
43
|
+
input: m.architecture.input_modalities,
|
|
44
|
+
output: m.architecture.output_modalities ?? ["text"],
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
41
47
|
}))
|
|
42
48
|
.filter((m) => m.id);
|
|
43
49
|
cache.set(root, { at: Date.now(), list });
|
package/dist/server/api.js
CHANGED
|
@@ -38,6 +38,12 @@ async function listModels(baseUrl, apiKey) {
|
|
|
38
38
|
completion: Number(m.pricing.completion ?? 0),
|
|
39
39
|
}
|
|
40
40
|
: undefined,
|
|
41
|
+
modalities: Array.isArray(m.architecture?.input_modalities) && m.architecture.input_modalities.length
|
|
42
|
+
? {
|
|
43
|
+
input: m.architecture.input_modalities,
|
|
44
|
+
output: m.architecture.output_modalities ?? ["text"],
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
41
47
|
}))
|
|
42
48
|
.filter((m) => m.id)
|
|
43
49
|
.sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -607,12 +613,21 @@ export function buildApp(master) {
|
|
|
607
613
|
if (!a)
|
|
608
614
|
return c.json({ error: "not found" }, 404);
|
|
609
615
|
const body = await c.req.json();
|
|
610
|
-
if (!body.text?.trim())
|
|
611
|
-
return c.json({ error: "text required" }, 400);
|
|
616
|
+
if (!body.text?.trim() && !body.images?.length)
|
|
617
|
+
return c.json({ error: "text or images required" }, 400);
|
|
618
|
+
// validate images: data URLs or http(s), size-capped (a 20MB base64 blob
|
|
619
|
+
// in the log would hurt every future replay of this session)
|
|
620
|
+
const images = (body.images ?? []).filter((im) => {
|
|
621
|
+
const u = String(im?.url ?? "");
|
|
622
|
+
if (u.startsWith("data:image/"))
|
|
623
|
+
return u.length < 12_000_000; // ~9MB binary
|
|
624
|
+
return /^https?:\/\//.test(u);
|
|
625
|
+
});
|
|
626
|
+
const text = body.text ?? "";
|
|
612
627
|
// returns immediately: the prompt is logged + broadcast now, delivered to
|
|
613
628
|
// the model at the next turn boundary (never blocks on a running agent).
|
|
614
629
|
// promptId ties the UI's pending echo to the later prompt-delivered note.
|
|
615
|
-
const promptId = a.enqueuePrompt(
|
|
630
|
+
const promptId = a.enqueuePrompt(text || "(see attached image)", "user", images.length ? images : undefined);
|
|
616
631
|
if (body.start !== false && a.status !== "running")
|
|
617
632
|
a.start("prompt");
|
|
618
633
|
return c.json({ ok: true, promptId, queued: a.snapshot().pendingPrompts });
|