mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
feat(mini-apps): capable models for app bg-tasks
- list-models tool: returns allowed model IDs (gateway for signed-in, config for BYOK) + a safe defaultModel, so the copilot picks a valid capable model instead of guessing rejected IDs - patch-background-task: clearModel option to reset a bad model/provider override (falls back to default); patchTask can now delete fields - build-mini-app skill: use list-models + set a capable model on data tasks
This commit is contained in:
parent
61f2bcff5a
commit
b548173593
3 changed files with 42 additions and 8 deletions
|
|
@ -112,10 +112,15 @@ buggy run can't corrupt the app with the wrong shape or wipe series with empties
|
|||
|
||||
The agent only RETURNS the data; \`mini-app-set-data\` writes \`data.json\`
|
||||
atomically (deterministic path + write — the agent never touches files). Set the
|
||||
manifest's \`agent\` field to the task's slug, and give the task a **capable model**
|
||||
(e.g. a Claude Sonnet / GPT-class model) via its model override — the default
|
||||
light model fabricates output and hallucinates tool names on side-effect tasks.
|
||||
Give it a sensible trigger (cron/window) from the background-task skill.
|
||||
manifest's \`agent\` field to the task's slug.
|
||||
|
||||
**Give the task a capable model.** The default bg-task model is a light one that
|
||||
fabricates output and hallucinates tool names on data/side-effect tasks. Set the
|
||||
task's \`model\` (via \`create-background-task\`) to a strong reasoning model —
|
||||
but only to an **allowed ID**: call **\`list-models\`** first and pick from what it
|
||||
returns (arbitrary IDs are rejected). \`list-models.defaultModel\` is always a safe
|
||||
capable choice. Give the task a sensible trigger (cron/window) from the
|
||||
background-task skill.
|
||||
|
||||
## 5. Finalize
|
||||
|
||||
|
|
@ -142,7 +147,8 @@ do this once the app is installed AND its data is populated, so it renders ready
|
|||
- **bg-task has no shell:** don't generate \`refresh.sh\` / \`executeCommand\`
|
||||
steps for the data agent — it can't run them headlessly.
|
||||
- **model:** set a capable model on any data/side-effect bg-task; the default is
|
||||
too weak and will fabricate results.
|
||||
too weak and will fabricate results. Use \`list-models\` to get allowed IDs —
|
||||
don't guess model IDs (unknown ones are rejected as "Model not allowed").
|
||||
|
||||
## Manifest schema (manifest.json)
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ const PatchBackgroundTaskInput = BackgroundTaskSchema.pick({
|
|||
slug: z.string().describe('The slug of the task to update (the folder name under bg-tasks/).'),
|
||||
triggers: TriggersSchema.optional().describe('Replace the triggers object. To remove all triggers (make manual-only) pass an empty object.'),
|
||||
projectDir: z.string().optional().describe("Point an existing task at a code repo (or change which one) to make it a coding task. Absolute path or ~/… to a local git repository with at least one commit. Same rules as on create."),
|
||||
clearModel: z.boolean().optional().describe("Reset the task's model/provider override so it falls back to the default. Use this to unstick a bad/rejected model value (do not also pass model)."),
|
||||
});
|
||||
|
||||
// Turn a user-supplied directory into a registered code project id. Reuses the
|
||||
|
|
@ -93,6 +94,7 @@ import type { ToolContext } from "./exec-tool.js";
|
|||
import { generateText } from "ai";
|
||||
import { createProvider } from "../../models/models.js";
|
||||
import { getDefaultModelAndProvider, resolveProviderConfig } from "../../models/defaults.js";
|
||||
import { listGatewayModels } from "../../models/gateway.js";
|
||||
import { captureLlmUsage } from "../../analytics/usage.js";
|
||||
import { getCurrentUseCase, withUseCase } from "../../analytics/use_case.js";
|
||||
import { isSignedIn } from "../../account/account.js";
|
||||
|
|
@ -1596,6 +1598,25 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
|||
}
|
||||
},
|
||||
},
|
||||
'list-models': {
|
||||
description: "List model IDs available for model overrides (e.g. to set a capable model on a background task). Signed-in users get the Rowboat gateway's allowed models; BYOK users get their configured model. Call this BEFORE setting a bg-task `model` so you pick a valid, allowed ID (arbitrary IDs are rejected). Returns { defaultModel, models }.",
|
||||
inputSchema: z.object({}),
|
||||
execute: async () => {
|
||||
try {
|
||||
if (await isSignedIn()) {
|
||||
const { providers } = await listGatewayModels();
|
||||
const models = providers.flatMap((p) => p.models.map((m) => m.id));
|
||||
const { model: defaultModel } = await getDefaultModelAndProvider();
|
||||
return { signedIn: true, defaultModel, models };
|
||||
}
|
||||
const { model, provider } = await getDefaultModelAndProvider();
|
||||
return { signedIn: false, defaultModel: model, provider, models: [model] };
|
||||
} catch (e) {
|
||||
return { error: e instanceof Error ? e.message : String(e) };
|
||||
}
|
||||
},
|
||||
isAvailable: async () => true,
|
||||
},
|
||||
'fetch-url': {
|
||||
description: "Fetch an HTTP(S) URL and return the response body as text. Use this to pull data from web APIs or pages (e.g. a JSON endpoint) — especially in background tasks, which have no shell. GET by default; supports POST with a body. Returns { ok, status, statusText, body } (body truncated if very large). For JSON, parse the returned body.",
|
||||
inputSchema: z.object({
|
||||
|
|
@ -1690,7 +1711,7 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
|||
execute: async (input: z.infer<typeof PatchBackgroundTaskInput>) => {
|
||||
try {
|
||||
const { patchTask } = await import("../../background-tasks/fileops.js");
|
||||
const { slug, projectDir, ...partial } = input;
|
||||
const { slug, projectDir, clearModel, ...partial } = input;
|
||||
let warning: string | undefined;
|
||||
if (projectDir) {
|
||||
const r = await resolveCodeProject(projectDir);
|
||||
|
|
@ -1698,7 +1719,7 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
|||
(partial as { projectId?: string }).projectId = r.projectId;
|
||||
warning = r.warning;
|
||||
}
|
||||
const result = await patchTask(slug, partial);
|
||||
const result = await patchTask(slug, partial, clearModel ? ['model', 'provider'] : []);
|
||||
return { success: true, task: result, ...(warning ? { warning } : {}) };
|
||||
} catch (err) {
|
||||
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
||||
|
|
|
|||
|
|
@ -81,13 +81,20 @@ export async function fetchTask(slug: string): Promise<BackgroundTask | null> {
|
|||
* structural edits (active toggle, instructions, triggers, model) and by the
|
||||
* runner for the `lastRun*` runtime fields.
|
||||
*/
|
||||
export async function patchTask(slug: string, partial: Partial<BackgroundTask>): Promise<BackgroundTask> {
|
||||
export async function patchTask(
|
||||
slug: string,
|
||||
partial: Partial<BackgroundTask>,
|
||||
clear: Array<keyof BackgroundTask> = [],
|
||||
): Promise<BackgroundTask> {
|
||||
return withFileLock(taskYamlPath(slug), async () => {
|
||||
const current = await fetchTask(slug);
|
||||
if (!current) {
|
||||
throw new Error(`Task '${slug}' not found`);
|
||||
}
|
||||
const next: BackgroundTask = { ...current, ...partial };
|
||||
// Allow explicitly clearing a field (e.g. reset model → falls back to the
|
||||
// default). A plain merge can't remove a key.
|
||||
for (const key of clear) delete next[key];
|
||||
await fs.writeFile(taskYamlPath(slug), stringifyYaml(next), 'utf-8');
|
||||
return next;
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue