Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cd0a5e0
propagate errors to render in the extension gui
jpoly1219 Jun 4, 2025
803e6c8
add overloads to unrollAssistant and unrollAssistantFromContent to ha…
jpoly1219 Jun 4, 2025
96ecb2c
fix a bug where icons will shrink depending on error message size
jpoly1219 Jun 4, 2025
76438cf
update deps
jpoly1219 Jun 4, 2025
d9400da
Merge branch 'main' of github.com:continuedev/continue into jacob/enh…
jpoly1219 Jun 4, 2025
364603e
fix prettier checks by pulling from main
jpoly1219 Jun 4, 2025
fdfcb00
Merge branch 'main' of github.com:continuedev/continue into jacob/enh…
jpoly1219 Jun 5, 2025
f7fc1b8
remove NEED_ERROR_PROPAGATION, pass asConfigResult as part of the opt…
jpoly1219 Jun 5, 2025
cdcb354
add asConfigResult field to BaseUnrollAssistantOptions
jpoly1219 Jun 5, 2025
fd51701
bump version
jpoly1219 Jun 5, 2025
36895bc
pass prettier checks
jpoly1219 Jun 5, 2025
bc23d5a
fix a bug where rawUnrolledYaml could pass as ConfigResult<AssistantU…
jpoly1219 Jun 5, 2025
8569ce3
Merge branch 'main' of github.com:continuedev/continue into jacob/enh…
jpoly1219 Jun 6, 2025
1dddae2
lazily unroll local assistant
jpoly1219 Jun 6, 2025
13b4b50
better error formatting
jpoly1219 Jun 6, 2025
f55f79e
bump version
jpoly1219 Jun 6, 2025
20b01d9
Merge branch 'main' of github.com:continuedev/continue into jacob/enh…
jpoly1219 Jun 6, 2025
f5dfc4d
pass prettier checks
jpoly1219 Jun 6, 2025
e871252
use existing util function to resolve URIs
jpoly1219 Jun 6, 2025
6ca0268
rever to printing full file path
jpoly1219 Jun 6, 2025
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
31 changes: 21 additions & 10 deletions core/config/yaml/loadYaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,15 @@ async function loadConfigYaml(options: {
// `Loading config.yaml from ${JSON.stringify(packageIdentifier)} with root path ${rootPath}`,
// );

let config =
overrideConfigYaml ??
const errors: ConfigValidationError[] = [];

let config: AssistantUnrolled | undefined;

if (overrideConfigYaml) {
config = overrideConfigYaml;
} else {
// This is how we allow use of blocks locally
(await unrollAssistant(
const unrollResult = await unrollAssistant(
packageIdentifier,
new RegistryClient({
accessToken: await controlPlaneClient.getAccessToken(),
Expand All @@ -139,17 +144,23 @@ async function loadConfigYaml(options: {
),
renderSecrets: true,
injectBlocks: allLocalBlocks,
asConfigResult: true,
},
));
);
config = unrollResult.config;
if (unrollResult.errors) {
errors.push(...unrollResult.errors);
}
}

const errors = isAssistantUnrolledNonNullable(config)
? validateConfigYaml(config)
: [
{
if (config) {
isAssistantUnrolledNonNullable(config)
? errors.push(...validateConfigYaml(config))
: errors.push({
fatal: true,
message: "Assistant includes blocks that don't exist",
},
];
});
}

if (errors?.some((error) => error.fatal)) {
return {
Expand Down
4 changes: 2 additions & 2 deletions extensions/vscode/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export function ErrorSection() {
}`}
>
{error.fatal ? (
<ExclamationCircleIcon className="mr-2 mt-1 h-3.5 w-3.5" />
<ExclamationCircleIcon className="mr-2 mt-1 h-3.5 w-3.5 shrink-0" />
) : (
<ExclamationTriangleIcon className="mr-2 mt-1 h-3.5 w-3.5" />
<ExclamationTriangleIcon className="mr-2 mt-1 h-3.5 w-3.5 shrink-0" />
)}
<p
className="m-0 whitespace-pre-wrap text-wrap"
Expand Down
140 changes: 122 additions & 18 deletions packages/config-yaml/src/load/unroll.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as YAML from "yaml";
import { ZodError } from "zod";
import { PlatformClient, Registry } from "../interfaces/index.js";
import { encodeSecretLocation } from "../interfaces/SecretResult.js";
import {
Expand All @@ -17,6 +18,7 @@ import {
configYamlSchema,
Rule,
} from "../schemas/index.js";
import { ConfigResult, ConfigValidationError } from "../validation.js";
import {
packageIdentifierToShorthandSlug,
useProxyForUnrenderedSecrets,
Expand All @@ -30,16 +32,25 @@ export function parseConfigYaml(configYaml: string): ConfigYaml {
if (result.success) {
return result.data;
}
throw new Error(
result.error.errors
.map((e) => `${e.path.join(".")}: ${e.message}`)
.join(""),
);

throw new Error(formatZodError(result.error), {
cause: "result.success was false",
});
} catch (e) {
console.log("Failed to parse rolled assistant:", configYaml);
throw new Error(
`Failed to parse assistant:\n${e instanceof Error ? e.message : e}`,
);
console.error("Failed to parse rolled assistant:", configYaml);
if (
e instanceof Error &&
"cause" in e &&
e.cause === "result.success was false"
) {
throw new Error(`Failed to parse assistant: ${e.message}`);
} else if (e instanceof ZodError) {
throw new Error(`Failed to parse assistant: ${formatZodError(e)}`);
} else {
throw new Error(
`Failed to parse assistant: ${e instanceof Error ? e.message : e}`,
);
}
}
}

Expand All @@ -49,9 +60,10 @@ export function parseAssistantUnrolled(configYaml: string): AssistantUnrolled {
const result = assistantUnrolledSchema.parse(parsed);
return result;
} catch (e: any) {
throw new Error(
console.error(
`Failed to parse unrolled assistant: ${e.message}\n\n${configYaml}`,
);
throw new Error(`Failed to parse unrolled assistant: ${formatZodError(e)}`);
}
}

Expand All @@ -61,7 +73,7 @@ export function parseBlock(configYaml: string): Block {
const result = blockSchema.parse(parsed);
return result;
} catch (e: any) {
throw new Error(`Failed to parse block: ${e.message}`);
throw new Error(`Failed to parse block: ${formatZodError(e)}`);
}
}

Expand Down Expand Up @@ -183,6 +195,7 @@ async function extractRenderedSecretsMap(
export interface BaseUnrollAssistantOptions {
renderSecrets: boolean;
injectBlocks?: PackageIdentifier[];
asConfigResult?: true;
}

export interface DoNotRenderSecretsUnrollAssistantOptions
Expand All @@ -204,14 +217,30 @@ export type UnrollAssistantOptions =
| DoNotRenderSecretsUnrollAssistantOptions
| RenderSecretsUnrollAssistantOptions;

// Overload to satisfy existing consumers of unrollAssistant.
export async function unrollAssistant(
id: PackageIdentifier,
registry: Registry,
options: UnrollAssistantOptions & { asConfigResult: true },
): Promise<ConfigResult<AssistantUnrolled>>;

export async function unrollAssistant(
id: PackageIdentifier,
registry: Registry,
options: UnrollAssistantOptions,
): Promise<AssistantUnrolled> {
): Promise<AssistantUnrolled>;

export async function unrollAssistant(
id: PackageIdentifier,
registry: Registry,
options: UnrollAssistantOptions,
): Promise<AssistantUnrolled | ConfigResult<AssistantUnrolled>> {
// Request the content from the registry
const rawContent = await registry.getContent(id);
return unrollAssistantFromContent(id, rawContent, registry, options);

const result = unrollAssistantFromContent(id, rawContent, registry, options);

return result;
}

function renderTemplateData(
Expand All @@ -236,7 +265,7 @@ export async function unrollAssistantFromContent(
rawYaml: string,
registry: Registry,
options: UnrollAssistantOptions,
): Promise<AssistantUnrolled> {
): Promise<AssistantUnrolled | ConfigResult<AssistantUnrolled>> {
// Parse string to Zod-validated YAML
let parsedYaml = parseConfigYaml(rawYaml);

Expand All @@ -245,10 +274,15 @@ export async function unrollAssistantFromContent(
parsedYaml,
registry,
options.injectBlocks,
options.asConfigResult ?? false,
);

// Back to a string so we can fill in template variables
const rawUnrolledYaml = YAML.stringify(unrolledAssistant);
const rawUnrolledYaml = options.asConfigResult
? YAML.stringify(
(unrolledAssistant as ConfigResult<AssistantUnrolled>).config,
)
: YAML.stringify(unrolledAssistant);

// Convert all of the template variables to FQSNs
// Secrets from the block will have the assistant slug prepended to the FQSN
Expand Down Expand Up @@ -277,14 +311,28 @@ export async function unrollAssistantFromContent(
options.orgScopeId,
options.onPremProxyUrl,
);

if (options.asConfigResult) {
return {
config: finalConfig,
errors: (unrolledAssistant as ConfigResult<AssistantUnrolled>).errors,
configLoadInterrupted: (
unrolledAssistant as ConfigResult<AssistantUnrolled>
).configLoadInterrupted,
};
}

return finalConfig;
}

export async function unrollBlocks(
assistant: ConfigYaml,
registry: Registry,
injectBlocks: PackageIdentifier[] | undefined,
): Promise<AssistantUnrolled> {
asConfigError: boolean,
): Promise<AssistantUnrolled | ConfigResult<AssistantUnrolled>> {
const errors: ConfigValidationError[] = [];

const unrolledAssistant: AssistantUnrolled = {
name: assistant.name,
version: assistant.version,
Expand Down Expand Up @@ -316,8 +364,23 @@ export async function unrollBlocks(
);
}
} catch (err) {
let msg = "";
if (
typeof unrolledBlock.uses !== "string" &&
"filePath" in unrolledBlock.uses
) {
msg = `${(err as Error).message}.\n> ${unrolledBlock.uses.filePath}`;
} else {
msg = `${(err as Error).message}.\n> ${JSON.stringify(unrolledBlock.uses)}`;
}

errors.push({
fatal: false,
message: msg,
});

console.error(
`Failed to unroll block ${unrolledBlock.uses}: ${(err as Error).message}`,
`Failed to unroll block ${JSON.stringify(unrolledBlock.uses)}: ${(err as Error).message}`,
);
sectionBlocks.push(null);
}
Expand Down Expand Up @@ -349,6 +412,11 @@ export async function unrollBlocks(
rules.push(block);
}
} catch (err) {
errors.push({
fatal: false,
message: `${(err as Error).message}:\n${rule.uses}`,
});

console.error(
`Failed to unroll block ${rule.uses}: ${(err as Error).message}`,
);
Expand Down Expand Up @@ -381,12 +449,36 @@ export async function unrollBlocks(
);
}
} catch (err) {
let msg = "";
if (injectBlock.uriType === "file") {
msg = `${(err as Error).message}.\n> ${injectBlock.filePath}`;
} else {
msg = `${(err as Error).message}.\n> ${injectBlock.fullSlug}`;
}
errors.push({
fatal: false,
message: msg,
});

console.error(
`Failed to unroll block ${injectBlock}: ${(err as Error).message}`,
`Failed to unroll block ${JSON.stringify(injectBlock)}: ${(err as Error).message}`,
);
}
}

if (asConfigError) {
const configResult: ConfigResult<AssistantUnrolled> = {
config: undefined,
errors: undefined,
configLoadInterrupted: false,
};
configResult.config = unrolledAssistant;
if (errors.length > 0) {
configResult.errors = errors;
}
return configResult;
}

return unrolledAssistant;
}

Expand Down Expand Up @@ -439,3 +531,15 @@ export function mergeOverrides<T extends Record<string, any>>(
}
return block;
}

function formatZodError(error: any): string {
if (error.errors && Array.isArray(error.errors)) {
return error.errors
.map((e: any) => {
const path = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
return `${path}${e.message}`;
})
.join(", ");
}
return error.message || "Validation failed";
}
Loading