feat(backend): migration to retire openai_direct runtime
This commit is contained in:
@@ -23,3 +23,35 @@ export const backfillAutoSyncDefaults = internalMutation({
|
|||||||
return { updated };
|
return { updated };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// One-shot migration retiring the legacy `openai_direct` runtime literal:
|
||||||
|
// rewrites any surviving `agentJobs`/`spoonAgentSettings` rows to `opencode`.
|
||||||
|
// `openai_direct` stays in the schema unions as a legacy-read literal; nothing
|
||||||
|
// writes it anymore. Run once post-deploy via
|
||||||
|
// `npx convex run migrations:migrateOpenAiDirectRuntime`. Idempotent.
|
||||||
|
export const migrateOpenAiDirectRuntime = internalMutation({
|
||||||
|
args: {},
|
||||||
|
returns: v.object({ patched: v.number() }),
|
||||||
|
handler: async (ctx) => {
|
||||||
|
let patched = 0;
|
||||||
|
for (const job of await ctx.db.query('agentJobs').collect()) {
|
||||||
|
if (job.runtime === 'openai_direct') {
|
||||||
|
await ctx.db.patch(job._id, {
|
||||||
|
runtime: 'opencode',
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
patched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const settings of await ctx.db.query('spoonAgentSettings').collect()) {
|
||||||
|
if (settings.runtime === 'openai_direct') {
|
||||||
|
await ctx.db.patch(settings._id, {
|
||||||
|
runtime: 'opencode',
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
patched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { patched };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { convexTest } from 'convex-test';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import { internal } from '../../convex/_generated/api.js';
|
||||||
|
import schema from '../../convex/schema';
|
||||||
|
|
||||||
|
const modules = import.meta.glob('../../convex/**/*.*s');
|
||||||
|
|
||||||
|
describe('migrateOpenAiDirectRuntime', () => {
|
||||||
|
test('rewrites legacy openai_direct rows to opencode and leaves others', async () => {
|
||||||
|
const t = convexTest(schema, modules);
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
const ids = await t.run(async (ctx) => {
|
||||||
|
const ownerId = await ctx.db.insert('users', {
|
||||||
|
email: 'owner@example.com',
|
||||||
|
name: 'Owner',
|
||||||
|
});
|
||||||
|
const spoonId = await ctx.db.insert('spoons', {
|
||||||
|
ownerId,
|
||||||
|
name: 'legacy-spoon',
|
||||||
|
provider: 'gitea',
|
||||||
|
upstreamOwner: 'upstream',
|
||||||
|
upstreamRepo: 'legacy-spoon',
|
||||||
|
upstreamDefaultBranch: 'main',
|
||||||
|
upstreamUrl: 'https://example.com/upstream/legacy-spoon',
|
||||||
|
forkOwner: 'owner',
|
||||||
|
forkRepo: 'legacy-spoon',
|
||||||
|
forkUrl: 'https://example.com/owner/legacy-spoon',
|
||||||
|
visibility: 'private',
|
||||||
|
maintenanceMode: 'watch',
|
||||||
|
syncCadence: 'daily',
|
||||||
|
productionRefStrategy: 'default_branch',
|
||||||
|
status: 'active',
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
});
|
||||||
|
const agentRequestId = await ctx.db.insert('agentRequests', {
|
||||||
|
spoonId,
|
||||||
|
ownerId,
|
||||||
|
prompt: 'legacy request',
|
||||||
|
status: 'running',
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
const settings = (runtime: 'openai_direct' | 'codex') => ({
|
||||||
|
spoonId,
|
||||||
|
ownerId,
|
||||||
|
enabled: true,
|
||||||
|
runtime,
|
||||||
|
branchPrefix: 'spoon/',
|
||||||
|
agentModel: 'gpt-5',
|
||||||
|
reasoningEffort: 'medium' as const,
|
||||||
|
maxJobDurationMs: 600000,
|
||||||
|
maxOutputBytes: 1000000,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
});
|
||||||
|
const job = (runtime: 'openai_direct' | 'codex') => ({
|
||||||
|
spoonId,
|
||||||
|
ownerId,
|
||||||
|
agentRequestId,
|
||||||
|
status: 'running' as const,
|
||||||
|
prompt: 'legacy job',
|
||||||
|
runtime,
|
||||||
|
baseBranch: 'main',
|
||||||
|
workBranch: 'spoon/legacy',
|
||||||
|
forkOwner: 'owner',
|
||||||
|
forkRepo: 'legacy-spoon',
|
||||||
|
forkUrl: 'https://example.com/owner/legacy-spoon',
|
||||||
|
upstreamOwner: 'upstream',
|
||||||
|
upstreamRepo: 'legacy-spoon',
|
||||||
|
selectedSecretIds: [],
|
||||||
|
model: 'gpt-5',
|
||||||
|
reasoningEffort: 'medium' as const,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
legacySettings: await ctx.db.insert(
|
||||||
|
'spoonAgentSettings',
|
||||||
|
settings('openai_direct'),
|
||||||
|
),
|
||||||
|
legacyJob: await ctx.db.insert('agentJobs', job('openai_direct')),
|
||||||
|
codexSettings: await ctx.db.insert(
|
||||||
|
'spoonAgentSettings',
|
||||||
|
settings('codex'),
|
||||||
|
),
|
||||||
|
codexJob: await ctx.db.insert('agentJobs', job('codex')),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await t.mutation(
|
||||||
|
internal.migrations.migrateOpenAiDirectRuntime,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.patched).toBe(2);
|
||||||
|
|
||||||
|
const after = await t.run(async (ctx) => ({
|
||||||
|
legacySettings: await ctx.db.get(ids.legacySettings),
|
||||||
|
legacyJob: await ctx.db.get(ids.legacyJob),
|
||||||
|
codexSettings: await ctx.db.get(ids.codexSettings),
|
||||||
|
codexJob: await ctx.db.get(ids.codexJob),
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(after.legacySettings?.runtime).toBe('opencode');
|
||||||
|
expect(after.legacyJob?.runtime).toBe('opencode');
|
||||||
|
expect(after.codexSettings?.runtime).toBe('codex');
|
||||||
|
expect(after.codexJob?.runtime).toBe('codex');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user