@kolbo/mcp 1.32.0 → 1.33.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/README.md +2 -1
- package/package.json +1 -1
- package/skill/GENERATED.md +1 -1
- package/skill/SKILL.md +8 -5
- package/skill/references/workflows/cost-and-validation.md +2 -0
- package/skill/references/workflows/troubleshooting.md +4 -0
- package/src/index.js +4 -1
- package/src/tools/_shared.js +1 -1
- package/src/tools/docs.js +119 -0
- package/src/tools/generate.js +60 -6
package/README.md
CHANGED
|
@@ -232,8 +232,9 @@ Every generation tool also accepts an optional `project_id` arg that routes the
|
|
|
232
232
|
| `list_presets` | Generation presets across image/video/music/text-to-video catalogs |
|
|
233
233
|
| `list_projects` | List owned + shared projects (id, name, role, is_default) — call first to resolve a project name into the `project_id` you pass to generation tools |
|
|
234
234
|
| `move_session` | Move a session (generation, chat, transcription…) and ALL its media to another project |
|
|
235
|
+
| `create_doc` / `list_docs` / `get_doc` / `update_doc` / `share_doc` / `delete_doc` | AI Docs (Magic Pad): author project-scoped HTML documents, edit them, get public share links |
|
|
235
236
|
| `check_credits` | Check credit balance |
|
|
236
|
-
| `get_generation_status` |
|
|
237
|
+
| `get_generation_status` | Check one or many generations (`generation_ids`); `wait=true` blocks server-side until done — replaces client polling loops |
|
|
237
238
|
|
|
238
239
|
## Environment Variables
|
|
239
240
|
|
package/package.json
CHANGED
package/skill/GENERATED.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AUTO-GENERATED — do not edit
|
|
2
2
|
|
|
3
3
|
This skill/ tree is mirrored from kolbo-code (the single source of truth)
|
|
4
|
-
by .github/workflows/sync-skill-to-plugin.yml — synced from kolbo-code@
|
|
4
|
+
by .github/workflows/sync-skill-to-plugin.yml — synced from kolbo-code@17e585e.
|
|
5
5
|
|
|
6
6
|
It is the skill that 'npx @kolbo/mcp install' deploys into the user's agent.
|
|
7
7
|
To change it, edit packages/opencode/skills/kolbo/ in kolbo-code and push;
|
package/skill/SKILL.md
CHANGED
|
@@ -35,7 +35,7 @@ Once per conversation, before any other Kolbo tool call:
|
|
|
35
35
|
|
|
36
36
|
1. **Run `check_credits`.** If it fails with "Session expired" / "Not authenticated", ask the user to run `kolbo auth login` (or their branded CLI command like `sapir auth login`) and reload the editor.
|
|
37
37
|
2. **If `list_models` returns empty**, MCP isn't wired — same fix.
|
|
38
|
-
3.
|
|
38
|
+
3. Use the balance ONLY for the low-balance check at this moment. **Never quote a "credits remaining" number later in the session** — coding/chat usage also deducts credits, so any remembered or computed balance is stale. Report only what each generation cost (`credits_used`); if the user asks what's left, run `check_credits` fresh right then.
|
|
39
39
|
|
|
40
40
|
If the user is on a whitelabel build (`sapir`, etc.), they must use their branded command — not `kolbo`. See `references/workflows/troubleshooting.md`.
|
|
41
41
|
|
|
@@ -154,8 +154,9 @@ A user-named tool — in any language — overrides every other rule. Recognized
|
|
|
154
154
|
- Auto-select → only from "Auto-selectable" section (models with a `summary`). Cheapest fit. Prefer `[RECOMMENDED]` when cost is similar.
|
|
155
155
|
- Never auto-select from "Named-only" section.
|
|
156
156
|
4. **Validate inputs** against model caps — see `references/workflows/cost-and-validation.md`.
|
|
157
|
-
5. **How calls work**: each tool blocks until generation is fully complete. Images: seconds. Video: minutes. Multiple tool calls in one response run concurrently.
|
|
158
|
-
6. **
|
|
157
|
+
5. **How calls work**: each tool blocks until generation is fully complete. Images: seconds. Video: minutes. Multiple tool calls in one response run concurrently. On hosts with live widgets the tool instead returns `submitted` instantly — the card updates on its own; you only need `get_generation_status` when a follow-up step needs the output URLs.
|
|
158
|
+
6. **Checking status — NEVER poll in a loop**: `get_generation_status` takes `wait=true` (blocks server-side until done, ~3 min) and `generation_ids` (check MANY generations in ONE call — returns `all_done` + which are still running). One `wait=true` call replaces any polling loop. If it comes back with some still processing, call it ONCE more with `wait=true` and the remaining ids.
|
|
159
|
+
7. **Share the URL** after success. Never fabricate URLs.
|
|
159
160
|
|
|
160
161
|
Model types for `list_models`: `text_to_img`, `image_editing`, `text_to_video`, `img_to_video`, `draw_to_video`, `video_to_video`, `elements`, `firstlastgenerations`, `lipsync-image`, `lipsync-video`, `music_gen`, `text_to_speech`, `text_to_sound`, `stt`, `text`, `3d_text_to_model`, `3d_image_to_model`, `3d_multi_image_to_model`, `3d_world`.
|
|
161
162
|
|
|
@@ -167,11 +168,13 @@ Full tables + formulas in `references/workflows/cost-and-validation.md`. Quick r
|
|
|
167
168
|
- **Otherwise confirm** via the labeled-question card: the parameters + the credit cost, suggest a cheaper alternative if one fits, wait for the user's pick. Never fire on defaults the user didn't choose.
|
|
168
169
|
- **Batch totalling 100+ credits**: run `check_credits` first.
|
|
169
170
|
- **Quote real cost**: after firing, log `credits_used` (from the tool result) to `.kolbo/production.md` — never `base × count`.
|
|
171
|
+
- **Never state "credits remaining" from arithmetic** (opening balance − generation costs). Coding/chat usage deducts credits too, so the math is always wrong. Report cost only; if the user asks for their balance, call `check_credits` fresh at that moment.
|
|
170
172
|
|
|
171
173
|
## Rate Limiting & Batch Generation
|
|
172
174
|
|
|
173
175
|
- `generate_image`: 30/min. All other generation tools: 10/min per type. 300/min global. `upload_media`: 300/min, no credit cost.
|
|
174
|
-
- **⚠️ NEVER re-fire a generation you already called.** Aborted / timed-out calls still process server-side. Run `get_generation_status` before retrying.
|
|
176
|
+
- **⚠️ NEVER re-fire a generation you already called.** Aborted / timed-out calls still process server-side. Run `get_generation_status` (with `wait=true`) before retrying.
|
|
177
|
+
- **Tracking a batch**: check ALL in-flight ids in ONE `get_generation_status` call with `generation_ids` + `wait=true`. Read `all_done` / `still_processing` from the response — do not check ids one by one, and never re-call without `wait`.
|
|
175
178
|
- **Batch ≤10 items**: output ALL tool calls in one response — they run concurrently.
|
|
176
179
|
- **Bulk >10 items**: real-world ceilings — `generate_image` 8–10 in-flight, image-edit 5–8, video tools 3–5, `generate_video_from_video` 3, music/speech/sound 5–8. Fire one batch → wait → fire next. Persist every `generation_id` in `.kolbo/production.md`.
|
|
177
180
|
- **`upload_media` external URLs first.** `files`/`source_images`/`image_url` only accept Kolbo-hosted URLs reliably; external URLs cause `400`.
|
|
@@ -224,7 +227,7 @@ A generation can fail three ways. Treat ALL as failure:
|
|
|
224
227
|
|
|
225
228
|
1. **Tool returns `error`** — explicit. Surface, suggest retry, log `generation_id`.
|
|
226
229
|
2. **Tool returns `completed` but `urls` is empty** — silent failure (NSFW filter, model OOM, upstream 5xx). Tell user "completed without an output — retrying" and re-fire ONCE. Do NOT log to `.kolbo/production.md`. Do NOT claim it worked.
|
|
227
|
-
3. **Tool hangs / never returns** — MCP poll timed out. Call `get_generation_status(generation_id)` IMMEDIATELY. The server might be done.
|
|
230
|
+
3. **Tool hangs / never returns** — MCP poll timed out. Call `get_generation_status(generation_id, wait=true)` IMMEDIATELY. The server might be done.
|
|
228
231
|
|
|
229
232
|
**Always:**
|
|
230
233
|
- Don't celebrate before reading the result. Verify `urls` is non-empty.
|
|
@@ -48,6 +48,8 @@ Apply when confirming cost before firing:
|
|
|
48
48
|
|
|
49
49
|
**Batch totalling 100+ credits:** run `check_credits` first and include the available balance in the summary.
|
|
50
50
|
|
|
51
|
+
**After generating, report cost only — never "credits remaining".** Coding/chat usage also deducts credits, so any balance you compute (opening balance − generation costs) is wrong by the time you say it. Quote `credits_used` per generation / batch total; if the user asks what's left, run `check_credits` fresh at that moment and quote that number.
|
|
52
|
+
|
|
51
53
|
## ⚠️ Quote Real Cost, Never Estimates (CRITICAL)
|
|
52
54
|
|
|
53
55
|
Pre-flight formulas above are for **preview only**. After firing, every generation returns `credits_used` (multiplier-adjusted total) and `credits_breakdown` (per-model attribution).
|
|
@@ -30,6 +30,10 @@ This re-wires the MCP configuration automatically. Then restart the session.
|
|
|
30
30
|
|
|
31
31
|
Wait 60s for the window to reset, retry only the failed calls. For batch image work prefer `generate_creative_director` over multiple `generate_image` calls. Full rate-limit details + retry sequence: see SKILL.md "Rate Limiting & Batch Generation".
|
|
32
32
|
|
|
33
|
+
## Checking generation status without spinning
|
|
34
|
+
|
|
35
|
+
`get_generation_status` supports `wait=true` (blocks server-side until the generation reaches a final state, up to ~3 min) and `generation_ids` (many ids in one call → returns `all_done`, `still_processing`, and per-generation results). **Never call it repeatedly in a loop** — one `wait=true` call replaces the loop. If some generations are still running after the wait window, call it ONCE more with `wait=true` and only the `still_processing` ids.
|
|
36
|
+
|
|
33
37
|
## Failure envelope from `get_generation_status`
|
|
34
38
|
|
|
35
39
|
When a generation fails, `get_generation_status` returns a structured `failure` field alongside `error`:
|
package/src/index.js
CHANGED
|
@@ -69,6 +69,7 @@ const { registerPresetTools } = require('./tools/presets');
|
|
|
69
69
|
const { registerAppBuilderTools } = require('./tools/app_builder');
|
|
70
70
|
const { registerArtifactTools } = require('./tools/artifacts');
|
|
71
71
|
const { registerProjectTools } = require('./tools/projects');
|
|
72
|
+
const { registerDocTools } = require('./tools/docs');
|
|
72
73
|
const { registerVoiceTools } = require('./tools/voices');
|
|
73
74
|
const { registerMusicLibraryTools } = require('./tools/music_library');
|
|
74
75
|
const { registerStockLibraryTools } = require('./tools/stock_library');
|
|
@@ -113,7 +114,8 @@ function createServer(opts = {}) {
|
|
|
113
114
|
'1. When the user names a project ("in my Acme project", "for the summer campaign"), call `list_projects` ONCE to resolve the name to an id, then pass that id as `project_id` on EVERY subsequent generate_* / chat_send_message / upload_media call in the conversation. The target project is per-call, NOT sticky — any call that omits `project_id` silently lands in the default "API Generations" bucket (flagged is_default:true), which users experience as their work going to the wrong project.',
|
|
114
115
|
'2. `list_projects` lists the user\'s platform projects (for generations/media/chat). `app_builder_list_projects` is a DIFFERENT tool that scopes App Builder coding sessions only — never use one where the other is meant.',
|
|
115
116
|
'3. Misplaced work is fixable: `move_media` / `bulk_move_media` / `move_folder_contents` move media items between projects; `move_session` moves a whole session (plus its media) to another project. If the user says a generation landed in the wrong project, move it rather than regenerating.',
|
|
116
|
-
'4. If the user has not mentioned any project, omit `project_id` — the default bucket is correct in that case. Do not ask which project to use unless the user\'s intent is ambiguous.'
|
|
117
|
+
'4. If the user has not mentioned any project, omit `project_id` — the default bucket is correct in that case. Do not ask which project to use unless the user\'s intent is ambiguous.',
|
|
118
|
+
'5. Written deliverables (plans, briefs, scripts, research summaries) can live in Kolbo too: author them as AI Docs with `create_doc` (project-scoped, editable in the app, shareable via `share_doc`).'
|
|
117
119
|
].join('\n')
|
|
118
120
|
});
|
|
119
121
|
|
|
@@ -133,6 +135,7 @@ function createServer(opts = {}) {
|
|
|
133
135
|
registerAppBuilderTools(server, client, toolOptions);
|
|
134
136
|
registerArtifactTools(server, client, toolOptions);
|
|
135
137
|
registerProjectTools(server, client, toolOptions);
|
|
138
|
+
registerDocTools(server, client, toolOptions);
|
|
136
139
|
registerMusicLibraryTools(server, client, toolOptions);
|
|
137
140
|
registerStockLibraryTools(server, client, toolOptions);
|
|
138
141
|
registerShortsCreatorTools(server, client, toolOptions);
|
package/src/tools/_shared.js
CHANGED
|
@@ -396,7 +396,7 @@ async function uiGenerating(p) {
|
|
|
396
396
|
const text = JSON.stringify({
|
|
397
397
|
status: 'submitted',
|
|
398
398
|
generation_id: p.gen.generation_id,
|
|
399
|
-
_widget_note: 'A live Kolbo widget is rendering this generation for the user (progress + final result + action buttons). Tell the user it is generating and the card above will update — do NOT poll in a loop. If you
|
|
399
|
+
_widget_note: 'A live Kolbo widget is rendering this generation for the user (progress + final result + action buttons). Tell the user it is generating and the card above will update — do NOT poll in a loop. If you need the output URLs (e.g. for a follow-up edit or a report), call get_generation_status ONCE with wait=true — it blocks until done. Tracking several generations? Pass ALL their ids in generation_ids in that one call.',
|
|
400
400
|
}, null, 2);
|
|
401
401
|
return uiResult(UI.generation, text, structured);
|
|
402
402
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* ⛔ BACKWARD COMPATIBILITY: Tool names and arg names below are a PUBLIC
|
|
2
|
+
* CONTRACT. Never rename, remove, or break an existing tool/arg — old cached
|
|
3
|
+
* `npx @kolbo/mcp` installs in the wild will break silently. Add new tools or
|
|
4
|
+
* new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
|
|
5
|
+
|
|
6
|
+
const { z } = require('zod');
|
|
7
|
+
const { projectIdField } = require('./_shared');
|
|
8
|
+
|
|
9
|
+
const CONTENT_GUIDE = 'HTML body content. Use clean semantic HTML the in-app editor understands: <h1>-<h3>, <p>, <ul>/<ol>/<li>, <table>, <blockquote>, <strong>/<em>, <a>. No <script>/<style>/<iframe> (stripped server-side). Write the FULL document yourself — this is where you author the doc.';
|
|
10
|
+
|
|
11
|
+
function registerDocTools(server, client) {
|
|
12
|
+
// ─── create_doc ────────────────────────────────────────────
|
|
13
|
+
server.tool(
|
|
14
|
+
'create_doc',
|
|
15
|
+
'Create an AI Doc (Magic Pad document) in the user\'s Kolbo workspace. YOU author the document: write complete, well-structured HTML content (plans, briefs, scripts, research summaries, meeting notes…) and save it here so the user can read and edit it in the Kolbo app. Docs are project-scoped — when the user is working in a named project, resolve it with `list_projects` and pass `project_id`. Returns the doc id; use `share_doc` afterwards if the user wants a public link.',
|
|
16
|
+
{
|
|
17
|
+
title: z.string().describe('Document title (shown in the app sidebar).'),
|
|
18
|
+
content: z.string().describe(CONTENT_GUIDE),
|
|
19
|
+
project_id: projectIdField
|
|
20
|
+
},
|
|
21
|
+
async ({ title, content, project_id }) => {
|
|
22
|
+
const body = { title, content };
|
|
23
|
+
if (project_id) body.project_id = project_id;
|
|
24
|
+
const result = await client.post('/v1/docs', body);
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: JSON.stringify({
|
|
29
|
+
doc: result.doc,
|
|
30
|
+
_hint: 'Doc created. The user can open it in the Kolbo app under the project\'s AI Docs. Call share_doc with shared:true to get a public link.'
|
|
31
|
+
}, null, 2)
|
|
32
|
+
}]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// ─── list_docs ─────────────────────────────────────────────
|
|
38
|
+
server.tool(
|
|
39
|
+
'list_docs',
|
|
40
|
+
'List the user\'s AI Docs (Magic Pad documents) across all projects, most recently updated first. Pass `project_id` (from `list_projects`) to narrow to one project. Returns id, title, project_id, share state, and timestamps — use `get_doc` to read a doc\'s content.',
|
|
41
|
+
{
|
|
42
|
+
project_id: z.string().optional().describe('Restrict to docs in one project (ObjectId from `list_projects`). Omit to list across all projects.'),
|
|
43
|
+
page: z.number().optional().describe('Page number, 1-indexed. Default: 1'),
|
|
44
|
+
limit: z.number().optional().describe('Results per page, max 50. Default: 20')
|
|
45
|
+
},
|
|
46
|
+
async ({ project_id, page, limit }) => {
|
|
47
|
+
const params = new URLSearchParams();
|
|
48
|
+
if (project_id) params.set('project_id', project_id);
|
|
49
|
+
if (page) params.set('page', String(page));
|
|
50
|
+
if (limit) params.set('limit', String(limit));
|
|
51
|
+
const qs = params.toString();
|
|
52
|
+
const result = await client.get(`/v1/docs${qs ? '?' + qs : ''}`);
|
|
53
|
+
return { content: [{ type: 'text', text: JSON.stringify({ docs: result.docs || [], pagination: result.pagination || null }, null, 2) }] };
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// ─── get_doc ───────────────────────────────────────────────
|
|
58
|
+
server.tool(
|
|
59
|
+
'get_doc',
|
|
60
|
+
'Fetch one AI Doc including its full HTML content. Use before editing an existing doc (read → modify → `update_doc`).',
|
|
61
|
+
{
|
|
62
|
+
doc_id: z.string().describe('The doc ObjectId (from create_doc or list_docs).')
|
|
63
|
+
},
|
|
64
|
+
async ({ doc_id }) => {
|
|
65
|
+
const result = await client.get(`/v1/docs/${encodeURIComponent(doc_id)}`);
|
|
66
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.doc, null, 2) }] };
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// ─── update_doc ────────────────────────────────────────────
|
|
71
|
+
server.tool(
|
|
72
|
+
'update_doc',
|
|
73
|
+
'Update an AI Doc\'s title and/or content. Content REPLACES the whole document — call `get_doc` first, apply the user\'s edits to the full HTML, and send the complete result back.',
|
|
74
|
+
{
|
|
75
|
+
doc_id: z.string().describe('The doc ObjectId to update.'),
|
|
76
|
+
title: z.string().optional().describe('New title. Omit to keep the current one.'),
|
|
77
|
+
content: z.string().optional().describe('Full replacement ' + CONTENT_GUIDE)
|
|
78
|
+
},
|
|
79
|
+
async ({ doc_id, title, content }) => {
|
|
80
|
+
const body = {};
|
|
81
|
+
if (title !== undefined) body.title = title;
|
|
82
|
+
if (content !== undefined) body.content = content;
|
|
83
|
+
const result = await client.put(`/v1/docs/${encodeURIComponent(doc_id)}`, body);
|
|
84
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.doc, null, 2) }] };
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// ─── share_doc ─────────────────────────────────────────────
|
|
89
|
+
server.tool(
|
|
90
|
+
'share_doc',
|
|
91
|
+
'Enable or disable public sharing of an AI Doc. When enabling, returns a stable public link (app.kolbo.ai/shared/magicpad/…) anyone can open — give it to the user. Set `editable` to also let link visitors edit the doc.',
|
|
92
|
+
{
|
|
93
|
+
doc_id: z.string().describe('The doc ObjectId.'),
|
|
94
|
+
shared: z.boolean().describe('true = publicly shared, false = private again (the link stops working).'),
|
|
95
|
+
editable: z.boolean().optional().describe('Whether link visitors can edit. Default: unchanged (false for new shares).')
|
|
96
|
+
},
|
|
97
|
+
async ({ doc_id, shared, editable }) => {
|
|
98
|
+
const body = { shared };
|
|
99
|
+
if (editable !== undefined) body.editable = editable;
|
|
100
|
+
const result = await client.patch(`/v1/docs/${encodeURIComponent(doc_id)}/share`, body);
|
|
101
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.doc, null, 2) }] };
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// ─── delete_doc ────────────────────────────────────────────
|
|
106
|
+
server.tool(
|
|
107
|
+
'delete_doc',
|
|
108
|
+
'Delete an AI Doc (soft delete — recoverable from the app\'s trash flow). Owner only. Confirm with the user before deleting anything they did not just create in this conversation.',
|
|
109
|
+
{
|
|
110
|
+
doc_id: z.string().describe('The doc ObjectId to delete.')
|
|
111
|
+
},
|
|
112
|
+
async ({ doc_id }) => {
|
|
113
|
+
const result = await client.delete(`/v1/docs/${encodeURIComponent(doc_id)}`);
|
|
114
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = { registerDocTools };
|
package/src/tools/generate.js
CHANGED
|
@@ -418,17 +418,71 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
418
418
|
// ─── get_generation_status ─────────────────────────────────
|
|
419
419
|
server.tool(
|
|
420
420
|
'get_generation_status',
|
|
421
|
-
'Check the status of
|
|
421
|
+
'Check the status of one or more generations. Use after a generation tool returned "submitted" (widget hosts) or timed out. Tracking SEVERAL concurrent generations? Pass them ALL in generation_ids — one call returns an all_done summary. Need the final result? Set wait=true and the server blocks until every generation finishes (up to ~3 min). NEVER call this tool repeatedly in a loop — one wait=true call replaces the whole loop.',
|
|
422
422
|
{
|
|
423
|
-
generation_id: z.string().describe('
|
|
423
|
+
generation_id: z.string().optional().describe('A single generation ID to check'),
|
|
424
|
+
generation_ids: z.array(z.string()).optional().describe('Multiple generation IDs to check in ONE call. Returns { all_done, pending, generations[] } — always prefer this over checking IDs one by one.'),
|
|
425
|
+
wait: z.boolean().optional().describe('If true, block until every generation reaches a terminal state (completed/failed), up to ~3 minutes, then return the final results. Use this instead of re-calling the tool in a loop.')
|
|
424
426
|
},
|
|
425
|
-
async ({ generation_id }) => {
|
|
426
|
-
const
|
|
427
|
+
async ({ generation_id, generation_ids, wait }) => {
|
|
428
|
+
const ids = (generation_ids && generation_ids.length > 0)
|
|
429
|
+
? generation_ids
|
|
430
|
+
: (generation_id ? [generation_id] : []);
|
|
431
|
+
if (ids.length === 0) throw new Error('Provide generation_id or generation_ids');
|
|
432
|
+
|
|
433
|
+
// One status check (or blocking poll) per id. Never let one bad id
|
|
434
|
+
// reject the whole batch — surface it as a failed entry instead.
|
|
435
|
+
const checkOne = async (id) => {
|
|
436
|
+
try {
|
|
437
|
+
if (wait) {
|
|
438
|
+
const result = await pollUntilDone(client, id, { interval: 5000, timeout: 180000 });
|
|
439
|
+
return { generation_id: id, ...result };
|
|
440
|
+
}
|
|
441
|
+
const result = await client.get(`/v1/generate/${encodeURIComponent(id)}/status`);
|
|
442
|
+
return { generation_id: id, ...result };
|
|
443
|
+
} catch (err) {
|
|
444
|
+
if (err.timedOut) {
|
|
445
|
+
return { generation_id: id, state: 'processing', note: 'Still running after 3 min of waiting — call get_generation_status again with wait=true.' };
|
|
446
|
+
}
|
|
447
|
+
if (err.name === 'GenerationFailedError') {
|
|
448
|
+
return { generation_id: id, state: 'failed', error: err.message };
|
|
449
|
+
}
|
|
450
|
+
return { generation_id: id, state: 'unknown', error: err.message };
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const results = await Promise.all(ids.map(checkOne));
|
|
455
|
+
|
|
456
|
+
const pending = results.filter(r => r.state !== 'completed' && r.state !== 'failed' && r.state !== 'cancelled');
|
|
457
|
+
const doneHint = 'ALL generations are in a final state — do NOT poll again. Report the results to the user.';
|
|
458
|
+
const pendingHint = wait
|
|
459
|
+
? 'Some generations are still running after the wait window. Call get_generation_status ONCE more with wait=true and the remaining generation_ids — do not spin without wait.'
|
|
460
|
+
: 'Some generations are still processing. Do NOT re-call this tool in a loop — call it ONCE with wait=true (and all pending generation_ids) to block until they finish.';
|
|
461
|
+
|
|
462
|
+
// Single-id calls keep the original flat shape — the generation widget
|
|
463
|
+
// polls this tool with { generation_id } and reads state/result at the
|
|
464
|
+
// top level.
|
|
465
|
+
if (!generation_ids || generation_ids.length === 0) {
|
|
466
|
+
const single = results[0];
|
|
467
|
+
single._hint = pending.length === 0
|
|
468
|
+
? 'This generation is in a FINAL state — do not poll it again.'
|
|
469
|
+
: pendingHint;
|
|
470
|
+
return {
|
|
471
|
+
content: [{ type: 'text', text: JSON.stringify(single, null, 2) }]
|
|
472
|
+
};
|
|
473
|
+
}
|
|
427
474
|
|
|
428
475
|
return {
|
|
429
476
|
content: [{
|
|
430
477
|
type: 'text',
|
|
431
|
-
text: JSON.stringify(
|
|
478
|
+
text: JSON.stringify({
|
|
479
|
+
all_done: pending.length === 0,
|
|
480
|
+
completed: results.filter(r => r.state === 'completed').length,
|
|
481
|
+
failed: results.filter(r => r.state === 'failed' || r.state === 'cancelled').length,
|
|
482
|
+
still_processing: pending.map(r => r.generation_id),
|
|
483
|
+
_hint: pending.length === 0 ? doneHint : pendingHint,
|
|
484
|
+
generations: results
|
|
485
|
+
}, null, 2)
|
|
432
486
|
}]
|
|
433
487
|
};
|
|
434
488
|
}
|
|
@@ -833,7 +887,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
833
887
|
return uiResult(UI.transcript, JSON.stringify({
|
|
834
888
|
status: 'submitted',
|
|
835
889
|
generation_id: startResponse.generation_id,
|
|
836
|
-
_widget_note: 'A live Kolbo transcription widget is rendering above — it shows progress, the transcript text, and SRT/TXT download buttons. Tell the user it is transcribing. If you need the transcript text for a follow-up step, call get_generation_status with this generation_id
|
|
890
|
+
_widget_note: 'A live Kolbo transcription widget is rendering above — it shows progress, the transcript text, and SRT/TXT download buttons. Tell the user it is transcribing. If you need the transcript text for a follow-up step, call get_generation_status ONCE with this generation_id and wait=true — it blocks until done; do NOT poll in a loop.',
|
|
837
891
|
}, null, 2), {
|
|
838
892
|
widget: 'transcript', phase: 'generating',
|
|
839
893
|
generation_id: startResponse.generation_id,
|