Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions background-charm-service/cast-admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CharmManager, compileRecipe } from "@commontools/charm";
import {
getCell,
getEntityId,
setBobbyServerUrl,
setBlobbyServerUrl,
storage,
} from "@commontools/runner";
import { type DID } from "@commontools/identity";
Expand Down Expand Up @@ -43,7 +43,7 @@ const identity = await getIdentity(
);

storage.setRemoteStorage(new URL(toolshedUrl));
setBobbyServerUrl(toolshedUrl);
setBlobbyServerUrl(toolshedUrl);

async function castRecipe() {
const spaceId = BG_SYSTEM_SPACE_ID;
Expand All @@ -64,18 +64,11 @@ async function castRecipe() {
// Load and compile the recipe first
console.log("Loading recipe...");
const recipeSrc = await Deno.readTextFile(recipePath!);
const recipe = await compileRecipe(recipeSrc, "recipe", []);

if (!recipe) {
throw new Error(`Failed to compile recipe from ${recipePath}`);
}

if (!cause) {
throw new Error("Cell ID is required");
}

console.log("Recipe compiled successfully");

const targetCell = getCell(
spaceId as DID,
cause,
Expand All @@ -100,6 +93,8 @@ async function castRecipe() {

// Create charm manager for the specified space
const charmManager = new CharmManager(session);
const recipe = await compileRecipe(recipeSrc, "recipe", charmManager);
console.log("Recipe compiled successfully");

const charm = await charmManager.runPersistent(
recipe,
Expand Down
4 changes: 2 additions & 2 deletions background-charm-service/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
isStream,
onConsole,
onError,
setBobbyServerUrl,
setBlobbyServerUrl,
setRecipeEnvironment,
storage,
} from "@commontools/runner";
Expand Down Expand Up @@ -88,7 +88,7 @@ async function initialize(
const apiUrl = new URL(toolshedUrl);
// Initialize storage and remote connection
storage.setRemoteStorage(apiUrl);
setBobbyServerUrl(toolshedUrl);
setBlobbyServerUrl(toolshedUrl);
storage.setSigner(identity);
setRecipeEnvironment({
apiUrl,
Expand Down
6 changes: 2 additions & 4 deletions charm/src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DEFAULT_MODEL_NAME, fixRecipePrompt } from "@commontools/llm";
import { Cell, getRecipe } from "@commontools/runner";
import { Cell, recipeManager } from "@commontools/runner";
import { Charm, CharmManager } from "./manager.ts";
import { getIframeRecipe } from "./iframe/recipe.ts";
import { extractUserCode, injectUserCode } from "./iframe/static.ts";
Expand All @@ -17,9 +17,7 @@ export const castSpellAsCharm = async (
if (recipeKey && argument) {
console.log("Syncing...");
const recipeId = recipeKey.replace("spell-", "");
await charmManager.syncRecipeBlobby(recipeId);

const recipe = getRecipe(recipeId);
const recipe = await charmManager.syncRecipeById(recipeId);
if (!recipe) return;

console.log("Casting...");
Expand Down
35 changes: 20 additions & 15 deletions charm/src/iframe/recipe.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JSONSchema, TYPE } from "@commontools/builder";
import { Charm, processSchema } from "../manager.ts";
import { Cell, getRecipe, getRecipeSrc } from "@commontools/runner";
import { JSONSchema } from "@commontools/builder";
import { Charm, getRecipeIdFromCharm } from "../manager.ts";
import { Cell, getEntityId, recipeManager } from "@commontools/runner";

export type IFrameRecipe = {
src: string;
Expand Down Expand Up @@ -61,20 +61,25 @@ function parseIframeRecipe(source: string): IFrameRecipe {
return JSON.parse(match[1]) as IFrameRecipe;
}

export const getIframeRecipe = (charm: Cell<Charm>) => {
const { src, recipeId, recipe } = getRecipeFrom(charm);
export const getIframeRecipe = (charm: Cell<Charm>): {
recipeId: string;
src?: string;
iframe?: IFrameRecipe;
} => {
const recipeId = getRecipeIdFromCharm(charm);
if (!recipeId) {
console.warn("No recipeId found for charm", getEntityId(charm));
return { recipeId, src: "", iframe: undefined };
}
const src = recipeManager.getRecipeMeta({ recipeId })?.src;
if (!src) {
console.warn("No src found for charm", getEntityId(charm));
return { recipeId };
}
try {
return { recipeId, iframe: parseIframeRecipe(src) };
return { recipeId, src, iframe: parseIframeRecipe(src) };
} catch (error) {
console.warn("Error parsing iframe recipe:", error);
return { recipeId, iframe: undefined };
return { recipeId, src };
}
};

export const getRecipeFrom = (charm: Cell<Charm>) => {
const recipeId = charm.getSourceCell(processSchema)?.get()?.[TYPE];
const recipe = getRecipe(recipeId)!;
const src = getRecipeSrc(recipeId)!;

return { recipeId, recipe, src };
};
8 changes: 2 additions & 6 deletions charm/src/imagine.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { Cell, getEntityId, isCell, isStream } from "@commontools/runner";
import { isObj } from "@commontools/utils";
import { JSONSchema } from "@commontools/builder";
import { Charm, CharmManager } from "./manager.ts";
import { getIframeRecipe } from "./iframe/recipe.ts";
import { extractUserCode } from "./iframe/static.ts";
import { Cell } from "@commontools/runner";
import { Charm } from "./manager.ts";

// Re-export workflow types and functions from workflow module
export type { WorkflowConfig, WorkflowType } from "./workflow.ts";
Expand Down
67 changes: 40 additions & 27 deletions charm/src/iterate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
Cell,
isCell,
isStream,
registerNewRecipe,
recipeManager,
runtime,
} from "@commontools/runner";
import { isObj } from "@commontools/utils";
Expand All @@ -12,11 +12,7 @@ import {
JSONSchemaMutable,
} from "@commontools/builder";
import { Charm, CharmManager, charmSourceCellSchema } from "./manager.ts";
import {
buildFullRecipe,
getIframeRecipe,
getRecipeFrom,
} from "./iframe/recipe.ts";
import { buildFullRecipe, getIframeRecipe } from "./iframe/recipe.ts";
import { buildPrompt, RESPONSE_PREFILL } from "./iframe/prompt.ts";
import {
applyDefaults,
Expand Down Expand Up @@ -152,26 +148,32 @@ export const generateNewRecipeVersion = async (
generationId?: string,
llmRequestId?: string,
) => {
const { iframe } = getIframeRecipe(parent);
const { recipe, recipeId } = getRecipeFrom(parent);
const parentInfo = getIframeRecipe(parent);
if (!parentInfo.recipeId) {
throw new Error("No recipeId found for charm");
}
const parentRecipe = await recipeManager.loadRecipe({
space: charmManager.getSpace(),
recipeId: parentInfo.recipeId,
});

const name = extractTitle(newRecipe.src, "<unknown>");
// If we have an iframe already, just spread everything
const fullSrc = buildFullRecipe(
iframe
? {
...iframe,
...newRecipe,
name: name,
}
// otherwise, we are editing a non-iframe recipe and need to grab/fill the schema
: {
argumentSchema: recipe.argumentSchema ?? { type: "object" },
resultSchema: recipe.resultSchema ?? { type: "object" },
...newRecipe,
name,
},
);
const argumentSchema =
(parentInfo.iframe
? parentInfo.iframe.argumentSchema
: parentRecipe.argumentSchema) ?? { type: "object" };
const resultSchema =
(parentInfo.iframe
? parentInfo.iframe.resultSchema
: parentRecipe.resultSchema) ?? { type: "object" };

const fullSrc = buildFullRecipe({
...parentInfo.iframe, // ignored if undefined
argumentSchema,
resultSchema,
...newRecipe,
name,
});

globalThis.dispatchEvent(
new CustomEvent("job-update", {
Expand All @@ -189,7 +191,7 @@ export const generateNewRecipeVersion = async (
fullSrc,
newRecipe.spec!,
parent.getSourceCell()?.key("argument"),
recipeId ? [recipeId] : undefined,
parentInfo.recipeId ? [parentInfo.recipeId] : undefined,
llmRequestId,
);

Expand Down Expand Up @@ -502,14 +504,25 @@ export async function castNewRecipe(
export async function compileRecipe(
recipeSrc: string,
spec: string,
charmManager: CharmManager,
parents?: string[],
) {
const recipe = await runtime.compile(recipeSrc);
if (!recipe) {
throw new Error("No default recipe found in the compiled exports.");
}
const parentsIds = parents?.map((id) => id.toString());
registerNewRecipe(recipe, recipeSrc, spec, parentsIds);
recipeManager.registerRecipe({
recipeId: recipeManager.generateRecipeId(recipe),
space: charmManager.getSpace(),
recipe,
recipeMeta: {
id: recipeManager.generateRecipeId(recipe),
src: recipeSrc,
spec,
parents: parentsIds,
},
});
return recipe;
}

Expand All @@ -521,7 +534,7 @@ export async function compileAndRunRecipe(
parents?: string[],
llmRequestId?: string,
): Promise<Cell<Charm>> {
const recipe = await compileRecipe(recipeSrc, spec, parents);
const recipe = await compileRecipe(recipeSrc, spec, charmManager, parents);
if (!recipe) {
throw new Error("Failed to compile recipe");
}
Expand Down
65 changes: 35 additions & 30 deletions charm/src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ import {
getCell,
getCellFromEntityId,
getEntityId,
getRecipe,
idle,
isCell,
isCellLink,
isDoc,
maybeGetCellLink,
recipeManager,
runSynced,
syncRecipeBlobby,
} from "@commontools/runner";
import { storage } from "@commontools/runner";
import { type Session } from "@commontools/identity";
Expand Down Expand Up @@ -267,7 +266,10 @@ export class CharmManager {
if (!recipeId) throw new Error("Cannot duplicate charm: missing recipe ID");

// Get the recipe
const recipe = getRecipe(recipeId);
const recipe = await recipeManager.loadRecipe({
recipeId,
space: this.space,
});
if (!recipe) throw new Error("Cannot duplicate charm: recipe not found");

// Get the original inputs
Expand Down Expand Up @@ -317,12 +319,15 @@ export class CharmManager {
charm = doc.asCell();
}

const recipeId = getRecipeIdFromCharm(charm);

// Make sure we have the recipe so we can run it!
let recipeId: string | undefined;
let recipe: Recipe | Module | undefined;
try {
recipeId = await this.syncRecipe(charm);
recipe = getRecipe(recipeId!)!;
recipe = await recipeManager.loadRecipe({
recipeId,
space: this.space,
});
} catch (e) {
console.warn("recipeId", recipeId);
console.warn("recipe", recipe);
Expand Down Expand Up @@ -1141,15 +1146,18 @@ export class CharmManager {
);
}

// Return Cell with argument content according to the schema of the charm.
getArgument<T = any>(charm: Cell<Charm | T>): Cell<T> | undefined {
// Return Cell with argument content of already loaded recipe according
// to the schema of the charm.
getArgument<T = any>(
charm: Cell<Charm | T>,
): Cell<T> {
const source = charm.getSourceCell(processSchema);
const recipeId = source?.get()?.[TYPE];
const recipe = getRecipe(recipeId);
const argumentSchema = recipe?.argumentSchema;
return source?.key("argument").asSchema(argumentSchema!) as
| Cell<T>
| undefined;
const recipeId = source?.get()?.[TYPE]!;
if (!recipeId) throw new Error("charm missing recipe ID");
const recipe = recipeManager.recipeById(recipeId);
if (!recipe) throw new Error(`Recipe ${recipeId} not loaded`);
// FIXME(ja): return should be Cell<Schema<T>> I think?
return source.key("argument").asSchema<T>(recipe.argumentSchema);
}

// note: removing a charm doesn't clean up the charm's cells
Expand Down Expand Up @@ -1248,39 +1256,36 @@ export class CharmManager {
);
}

await this.syncRecipe(charm);

return charm;
}

// FIXME(JA): this really really really needs to be revisited
async syncRecipe(charm: Cell<Charm>): Promise<string> {
async syncRecipe(charm: Cell<Charm>) {
await storage.syncCell(charm);

const sourceCell = charm.getSourceCell();
if (!sourceCell) throw new Error("charm missing source cell");

await storage.syncCell(sourceCell);

const recipeId = sourceCell.get()?.[TYPE];
if (!recipeId) throw new Error("charm missing recipe ID");

await Promise.all([
this.syncRecipeCells(recipeId),
this.syncRecipeBlobby(recipeId),
]);
return recipeId;
}

async syncRecipeCells(recipeId: string) {
// NOTE(ja): this doesn't sync recipe to storage
if (recipeId) await storage.syncCellById(this.space, { "/": recipeId });
await this.syncRecipeById(recipeId);
}

// FIXME(ja): blobby seems to be using toString not toJSON
async syncRecipeBlobby(recipeId: string) {
await syncRecipeBlobby(recipeId);
async syncRecipeById(recipeId: string) {
return await recipeManager.ensureRecipeAvailable({
recipeId,
space: this.space,
});
}

async sync(entity: Cell<any>, waitForStorage: boolean = false) {
await storage.syncCell(entity, waitForStorage);
}
}

export const getRecipeIdFromCharm = (charm: Cell<Charm>): string => {
return charm.getSourceCell(processSchema)?.get()?.[TYPE];
};
Loading