Skip to content

Commit d0f6527

Browse files
authored
feat(runner/llm-builtin): expose schema on result self-link and use in llm tools (commontoolsinc#2085)
feat(runner/llm-builtin): expose schema on result self-link and use in llm tools also makes a bunch of parts that were async sync now, which might fix some races allow schema tool for all cells + clean up redundancy
1 parent 50e578c commit d0f6527

File tree

2 files changed

+44
-82
lines changed

2 files changed

+44
-82
lines changed

packages/runner/src/builtins/llm-dialog.ts

Lines changed: 36 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -88,38 +88,17 @@ function normalizeInputSchema(schemaLike: unknown): JSONSchema {
8888
* - Prefer a non-empty recipe.resultSchema if recipe is loaded
8989
* - Otherwise derive a simple object schema from the current value
9090
*/
91-
async function getCharmResultSchemaAsync(
92-
runtime: IRuntime,
93-
charm: Cell<any>,
94-
): Promise<JSONSchema | undefined> {
95-
try {
96-
const source = charm.getSourceCell();
97-
const recipeId = source?.get()?.[TYPE];
98-
if (recipeId) {
99-
await runtime.recipeManager.loadRecipe(recipeId, charm.space);
100-
}
101-
return (
102-
getLoadedRecipeResultSchema(runtime, charm) ??
103-
buildMinimalSchemaFromValue(charm)
104-
);
105-
} catch (_e) {
106-
return buildMinimalSchemaFromValue(charm);
107-
}
108-
}
109-
110-
function getLoadedRecipeResultSchema(
111-
runtime: IRuntime | undefined,
112-
charm: Cell<any>,
91+
function getCellSchema(
92+
cell: Cell<unknown>,
11393
): JSONSchema | undefined {
114-
const source = charm.getSourceCell();
115-
const recipeId = source?.get()?.[TYPE];
116-
const recipe = recipeId
117-
? runtime?.recipeManager.recipeById(recipeId)
118-
: undefined;
119-
if (recipe?.resultSchema !== undefined) {
120-
return recipe.resultSchema;
121-
}
122-
return undefined;
94+
return cell.schema ??
95+
// If cell has a source cell, any resultRef has a schema attached
96+
cell.getSourceCell<{ resultRef: Cell<unknown> }>({
97+
type: "object",
98+
properties: { resultRef: { asCell: true } },
99+
})?.get()?.resultRef?.schema ??
100+
// Otherwise, derive a simple object schema from the current value
101+
buildMinimalSchemaFromValue(cell);
123102
}
124103

125104
function buildMinimalSchemaFromValue(charm: Cell<any>): JSONSchema | undefined {
@@ -850,11 +829,11 @@ function buildToolCatalog(
850829
* from the attachments list. This is appended to the system prompt so the LLM
851830
* has immediate context about available charms without needing to call schema() first.
852831
*/
853-
async function buildAttachmentsSchemaDocumentation(
832+
function buildAttachmentsSchemaDocumentation(
854833
runtime: IRuntime,
855834
space: MemorySpace,
856835
attachments: Cell<Attachment[]>,
857-
): Promise<string> {
836+
): string {
858837
const currentAttachments = attachments.get() || [];
859838
if (currentAttachments.length === 0) {
860839
return "";
@@ -877,7 +856,7 @@ async function buildAttachmentsSchemaDocumentation(
877856
}
878857

879858
// Get schema for the cell
880-
const schema = await getCharmResultSchemaAsync(runtime, cell);
859+
const schema = getCellSchema(cell);
881860
if (schema) {
882861
const schemaJson = JSON.stringify(schema, null, 2);
883862
schemaEntries.push(
@@ -911,7 +890,7 @@ type ResolvedToolCall =
911890
| { type: "listAttachments"; call: LLMToolCall }
912891
| { type: "addAttachment"; call: LLMToolCall; path: string; name: string }
913892
| { type: "removeAttachment"; call: LLMToolCall; path: string }
914-
| { type: "schema"; call: LLMToolCall; charm: Cell<any> }
893+
| { type: "schema"; call: LLMToolCall; cellRef: Cell<any> }
915894
| { type: "read"; call: LLMToolCall; cellRef: Cell<any> }
916895
| { type: "navigateTo"; call: LLMToolCall; cellRef: Cell<any> }
917896
| {
@@ -997,42 +976,31 @@ function resolveToolCall(
997976
};
998977
}
999978

979+
const target = extractStringField(
980+
toolCallPart.input,
981+
"path",
982+
"/of:bafyabc123/path",
983+
);
984+
985+
const link = parseLLMFriendlyLink(target, space);
986+
const cellRef = runtime.getCellFromLink(link);
987+
1000988
if (name === SCHEMA_TOOL_NAME) {
1001989
const charmName = extractStringField(
1002990
toolCallPart.input,
1003991
"path",
1004992
"/of:bafyabc123/path",
1005993
);
1006-
const charm = catalog.charmMap.get(charmName);
1007-
if (!charm) {
1008-
throw new Error(
1009-
`Unknown charm "${charmName}". Use listAttachments() for options.`,
1010-
);
1011-
}
1012994
return {
1013995
type: "schema",
1014-
charm,
996+
cellRef,
1015997
call: { id, name, input: { charm: charmName } },
1016998
};
1017999
}
10181000

1019-
const target = extractStringField(
1020-
toolCallPart.input,
1021-
"path",
1022-
"/of:bafyabc123/path",
1023-
);
1024-
1025-
const link = parseLLMFriendlyLink(target, space);
1026-
10271001
if (name === READ_TOOL_NAME) {
10281002
// Get cell reference from the link - works for any valid handle
1029-
const cellRef = runtime.getCellFromLink(link);
1030-
if (!cellRef) {
1031-
throw new Error(
1032-
`Could not resolve handle "${id}" to a cell. The handle may not exist in this space.`,
1033-
);
1034-
}
1035-
if (isStream(cellRef)) {
1003+
if (isStream(cellRef.resolveAsCell())) {
10361004
throw new Error(`Path resolves to a handler; use run("${target}").`);
10371005
}
10381006

@@ -1044,32 +1012,21 @@ function resolveToolCall(
10441012
}
10451013

10461014
if (name === NAVIGATE_TO_TOOL_NAME) {
1047-
// Get cell reference from the link - works for any valid handle
1048-
const cellRef = runtime.getCellFromLink(link);
1049-
if (!cellRef) {
1050-
throw new Error(
1051-
`Could not resolve handle "${id}" to a cell. The handle may not exist in this space.`,
1052-
);
1053-
}
1054-
10551015
return {
10561016
type: "navigateTo",
10571017
cellRef,
10581018
call: { id, name, input: { path: target } },
10591019
};
10601020
}
10611021

1062-
// For run(), resolve the cell and check if it's a handler or pattern
1063-
const cellRef: Cell<any> = runtime.getCellFromLink(link);
1064-
10651022
// Get optional charm metadata for validation (only used for handlers)
10661023
const charmEntry = catalog.handleMap.get(link.id);
10671024
const charm = charmEntry?.charm;
10681025

1069-
if (isStream(cellRef)) {
1026+
if (isStream(cellRef.resolveAsCell())) {
10701027
return {
10711028
type: "run",
1072-
handler: cellRef as any,
1029+
handler: cellRef as unknown as Stream<any>,
10731030
charm,
10741031
call: {
10751032
id,
@@ -1079,13 +1036,13 @@ function resolveToolCall(
10791036
};
10801037
}
10811038

1082-
const pattern = (cellRef as Cell<any>).key("pattern")
1039+
const pattern = cellRef.key("pattern")
10831040
.getRaw() as unknown as Readonly<Recipe> | undefined;
10841041
if (pattern) {
10851042
return {
10861043
type: "run",
10871044
pattern,
1088-
extraParams: (cellRef as Cell<any>).key("extraParams").get() ?? {},
1045+
extraParams: cellRef.key("extraParams").get() ?? {},
10891046
charm,
10901047
call: {
10911048
id,
@@ -1393,13 +1350,11 @@ function handleListAttachments(
13931350
/**
13941351
* Handles the schema tool call.
13951352
*/
1396-
async function handleSchema(
1397-
runtime: IRuntime,
1353+
function handleSchema(
13981354
resolved: ResolvedToolCall & { type: "schema" },
1399-
): Promise<{ type: string; value: any }> {
1400-
const schema = await getCharmResultSchemaAsync(runtime, resolved.charm) ??
1401-
{};
1402-
const value = JSON.parse(JSON.stringify(schema ?? {}));
1355+
): { type: string; value: any } {
1356+
const schema = getCellSchema(resolved.cellRef) ?? {};
1357+
const value = JSON.parse(JSON.stringify(schema));
14031358
return { type: "json", value };
14041359
}
14051360

@@ -1579,7 +1534,7 @@ async function invokeToolCall(
15791534
}
15801535

15811536
if (resolved.type === "schema") {
1582-
return await handleSchema(runtime, resolved);
1537+
return handleSchema(resolved);
15831538
}
15841539

15851540
if (resolved.type === "read") {
@@ -1820,7 +1775,7 @@ export function llmDialog(
18201775
};
18211776
}
18221777

1823-
async function startRequest(
1778+
function startRequest(
18241779
tx: IExtendedStorageTransaction,
18251780
runtime: IRuntime,
18261781
space: MemorySpace,
@@ -1842,7 +1797,7 @@ async function startRequest(
18421797
const toolCatalog = buildToolCatalog(runtime, toolsCell);
18431798

18441799
// Build charm schemas documentation from attachments and append to system prompt
1845-
const charmSchemasDocs = await buildAttachmentsSchemaDocumentation(
1800+
const charmSchemasDocs = buildAttachmentsSchemaDocumentation(
18461801
runtime,
18471802
space,
18481803
attachments,

packages/runner/src/runner.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,14 @@ export class Runner implements IRunner {
302302
processCell.withTx(tx).setRaw({
303303
...processCell.getRaw({ meta: ignoreReadForScheduling }),
304304
[TYPE]: recipeId || "unknown",
305-
resultRef: resultCell.getAsLink({ base: processCell }),
305+
resultRef: (recipe.resultSchema !== undefined
306+
? resultCell.asSchema(recipe.resultSchema).getAsLink({
307+
base: processCell,
308+
includeSchema: true,
309+
})
310+
: resultCell.getAsLink({
311+
base: processCell,
312+
})),
306313
internal,
307314
...(recipeId !== undefined) ? { spell: getSpellLink(recipeId) } : {},
308315
});

0 commit comments

Comments
 (0)