Skip to content

Commit 94fce23

Browse files
committed
Merge branch 'dev' of https://github.com/microsoft/genaiscript into dev
2 parents 3c0d828 + 0be10f2 commit 94fce23

File tree

4 files changed

+243
-2
lines changed

4 files changed

+243
-2
lines changed

packages/core/src/env.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ import type { TraceOptions } from "./trace.js";
6969
import type { CancellationOptions } from "./cancellation.js";
7070
import { genaiscriptDebug } from "./debug.js";
7171
import { YAMLTryParse } from "./yaml.js";
72+
import { JSON5TryParse } from "./json5.js";
73+
import type { PromptArgs, PromptScript } from "./types.js";
7274
const dbg = genaiscriptDebug("config:env");
7375

7476
/**
@@ -122,6 +124,50 @@ export function findEnvVar(
122124
return undefined;
123125
}
124126

127+
/**
128+
* Parses default script metadata from GENAISCRIPT_DEFAULT_SCRIPT_META environment variable.
129+
* The environment variable should contain a JSON payload of PromptScript metadata.
130+
* This metadata gets merged last into the main script metadata object.
131+
*
132+
* @param env - The environment variables as key-value pairs.
133+
* @returns A PromptArgs object containing the parsed metadata, or undefined if no valid metadata found.
134+
*/
135+
export function parseDefaultMetaFromEnv(env: Record<string, string>): Partial<PromptArgs> | undefined {
136+
const envValue = env.GENAISCRIPT_DEFAULT_SCRIPT_META;
137+
if (!envValue) {
138+
dbg("GENAISCRIPT_DEFAULT_SCRIPT_META not found in environment variables");
139+
return undefined;
140+
}
141+
142+
dbg(`found GENAISCRIPT_DEFAULT_SCRIPT_META: ${envValue}`);
143+
144+
try {
145+
const parsed = JSON5TryParse(envValue);
146+
if (!parsed || typeof parsed !== "object") {
147+
dbg("GENAISCRIPT_DEFAULT_SCRIPT_META could not be parsed as valid JSON object");
148+
return undefined;
149+
}
150+
151+
dbg(`parsed GENAISCRIPT_DEFAULT_SCRIPT_META: %O`, parsed);
152+
153+
// Filter to only include valid PromptArgs fields (exclude text, id, jsSource, defTools, resolvedSystem)
154+
const excludedFields = new Set(['text', 'id', 'jsSource', 'defTools', 'resolvedSystem']);
155+
const filtered: Partial<PromptArgs> = {};
156+
157+
for (const [key, value] of Object.entries(parsed)) {
158+
if (!excludedFields.has(key)) {
159+
(filtered as any)[key] = value;
160+
}
161+
}
162+
163+
dbg(`filtered GENAISCRIPT_DEFAULT_SCRIPT_META: %O`, filtered);
164+
return filtered;
165+
} catch (error) {
166+
dbg(`failed to parse GENAISCRIPT_DEFAULT_SCRIPT_META: ${error}`);
167+
return undefined;
168+
}
169+
}
170+
125171
/**
126172
* Parses default configuration values from the provided environment variables.
127173
*

packages/core/src/template.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { deleteUndefinedValues } from "./cleaners.js";
1515
import { markdownScriptParse } from "./markdownscript.js";
1616
import { readJSON } from "./fs.js";
1717
import { frontmatterTryParse } from "./frontmatter.js";
18+
import { parseDefaultMetaFromEnv } from "./env.js";
1819
import type {
1920
PromptArgs,
2021
PromptScript,
@@ -241,5 +242,15 @@ export async function parsePromptScript(filename: string, content: string) {
241242
};
242243
}
243244

245+
// Parse and merge default metadata from environment variables (last to take priority)
246+
const envDefaults = parseDefaultMetaFromEnv(process.env);
247+
if (envDefaults?.metadata) {
248+
// Only merge metadata field from environment defaults
249+
script.metadata = metadataValidate({
250+
...(script.metadata || {}),
251+
...(envDefaults.metadata || {}), // env metadata takes precedence
252+
});
253+
}
254+
244255
return script;
245256
}

packages/core/test/env.test.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, test, assert } from "vitest";
2-
import { parseAllowedDomains } from "../src/env.js";
2+
import { parseAllowedDomains, parseDefaultMetaFromEnv } from "../src/env.js";
33

44
describe("env", () => {
55
describe("parseAllowedDomains", () => {
@@ -53,4 +53,83 @@ describe("env", () => {
5353
assert.deepStrictEqual(result, ["github.com"]);
5454
});
5555
});
56+
57+
describe("parseDefaultMetaFromEnv", () => {
58+
test("returns undefined when GENAISCRIPT_DEFAULT_SCRIPT_META not set", () => {
59+
const result = parseDefaultMetaFromEnv({});
60+
assert.strictEqual(result, undefined);
61+
});
62+
63+
test("parses valid JSON metadata", () => {
64+
const env = {
65+
GENAISCRIPT_DEFAULT_SCRIPT_META: '{"temperature": 0.5, "model": "gpt-4", "title": "Default Title"}'
66+
};
67+
const result = parseDefaultMetaFromEnv(env);
68+
assert.deepStrictEqual(result, {
69+
temperature: 0.5,
70+
model: "gpt-4",
71+
title: "Default Title"
72+
});
73+
});
74+
75+
test("parses valid JSON5 metadata", () => {
76+
const env = {
77+
GENAISCRIPT_DEFAULT_SCRIPT_META: '{temperature: 0.5, model: "gpt-4", unlisted: true}'
78+
};
79+
const result = parseDefaultMetaFromEnv(env);
80+
assert.deepStrictEqual(result, {
81+
temperature: 0.5,
82+
model: "gpt-4",
83+
unlisted: true
84+
});
85+
});
86+
87+
test("handles metadata with nested objects", () => {
88+
const env = {
89+
GENAISCRIPT_DEFAULT_SCRIPT_META: '{"metadata": {"key1": "value1", "key2": "value2"}, "vars": {"var1": "val1"}}'
90+
};
91+
const result = parseDefaultMetaFromEnv(env);
92+
assert.deepStrictEqual(result, {
93+
metadata: {
94+
key1: "value1",
95+
key2: "value2"
96+
},
97+
vars: {
98+
var1: "val1"
99+
}
100+
});
101+
});
102+
103+
test("returns undefined for invalid JSON", () => {
104+
const env = {
105+
GENAISCRIPT_DEFAULT_SCRIPT_META: 'invalid json {'
106+
};
107+
const result = parseDefaultMetaFromEnv(env);
108+
assert.strictEqual(result, undefined);
109+
});
110+
111+
test("returns undefined for non-object values", () => {
112+
const env = {
113+
GENAISCRIPT_DEFAULT_SCRIPT_META: '"just a string"'
114+
};
115+
const result = parseDefaultMetaFromEnv(env);
116+
assert.strictEqual(result, undefined);
117+
});
118+
119+
test("returns undefined for null values", () => {
120+
const env = {
121+
GENAISCRIPT_DEFAULT_SCRIPT_META: 'null'
122+
};
123+
const result = parseDefaultMetaFromEnv(env);
124+
assert.strictEqual(result, undefined);
125+
});
126+
127+
test("handles empty object", () => {
128+
const env = {
129+
GENAISCRIPT_DEFAULT_SCRIPT_META: '{}'
130+
};
131+
const result = parseDefaultMetaFromEnv(env);
132+
assert.deepStrictEqual(result, {});
133+
});
134+
});
56135
});

packages/core/test/template.test.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, test, assert } from "vitest"
1+
import { describe, test, assert, vi, beforeEach, afterEach } from "vitest"
22
import { parsePromptScript } from "../src/template.js"
33

44
describe("template.ts - frontmatter parameters", () => {
@@ -119,4 +119,109 @@ Configuration test with {{items}} and {{config}}.`
119119
maximum: 1
120120
})
121121
})
122+
})
123+
124+
describe("template.ts - environment variable default metadata", () => {
125+
let originalEnv: any
126+
127+
beforeEach(() => {
128+
originalEnv = { ...process.env }
129+
})
130+
131+
afterEach(() => {
132+
process.env = originalEnv
133+
})
134+
135+
test("should merge environment default metadata into script metadata field", async () => {
136+
process.env.GENAISCRIPT_DEFAULT_SCRIPT_META = '{"metadata": {"env_key": "env_value", "shared_key": "env_shared"}}'
137+
138+
const content = `script({
139+
title: "Test Script",
140+
description: "A test script",
141+
metadata: {
142+
script_key: "script_value",
143+
shared_key: "script_shared"
144+
}
145+
})
146+
147+
Hello world!`
148+
149+
const script = await parsePromptScript("test.genai.mts", content)
150+
151+
assert.strictEqual(script.title, "Test Script")
152+
assert.strictEqual(script.description, "A test script")
153+
assert.ok(script.metadata)
154+
assert.strictEqual(script.metadata.env_key, "env_value")
155+
assert.strictEqual(script.metadata.script_key, "script_value")
156+
// Environment metadata should take precedence for shared keys
157+
assert.strictEqual(script.metadata.shared_key, "env_shared")
158+
})
159+
160+
test("should handle environment metadata without existing script metadata", async () => {
161+
process.env.GENAISCRIPT_DEFAULT_SCRIPT_META = '{"metadata": {"env_key": "env_value"}}'
162+
163+
const content = `script({
164+
title: "Test Script"
165+
})
166+
167+
Hello world!`
168+
169+
const script = await parsePromptScript("test.genai.mts", content)
170+
171+
assert.strictEqual(script.title, "Test Script")
172+
assert.ok(script.metadata)
173+
assert.strictEqual(script.metadata.env_key, "env_value")
174+
})
175+
176+
177+
test("should work without environment variable set", async () => {
178+
delete process.env.GENAISCRIPT_DEFAULT_SCRIPT_META
179+
180+
const content = `script({
181+
title: "Test Script",
182+
metadata: {
183+
original: "value"
184+
}
185+
})
186+
187+
Hello world!`
188+
189+
const script = await parsePromptScript("test.genai.mts", content)
190+
191+
assert.strictEqual(script.title, "Test Script")
192+
assert.ok(script.metadata)
193+
assert.strictEqual(script.metadata.original, "value")
194+
})
195+
196+
test("should handle invalid JSON in environment variable gracefully", async () => {
197+
process.env.GENAISCRIPT_DEFAULT_SCRIPT_META = 'invalid json {'
198+
199+
const content = `script({
200+
title: "Test Script"
201+
})
202+
203+
Hello world!`
204+
205+
const script = await parsePromptScript("test.genai.mts", content)
206+
207+
assert.strictEqual(script.title, "Test Script")
208+
// Should not throw an error, just ignore the invalid env var
209+
})
210+
211+
test("should handle environment metadata with nested objects", async () => {
212+
process.env.GENAISCRIPT_DEFAULT_SCRIPT_META = '{"metadata": {"nested": {"key": "value"}, "simple": "data"}}'
213+
214+
const content = `script({
215+
title: "Test Script"
216+
})
217+
218+
Hello world!`
219+
220+
const script = await parsePromptScript("test.genai.mts", content)
221+
222+
assert.strictEqual(script.title, "Test Script")
223+
assert.ok(script.metadata)
224+
assert.deepEqual(script.metadata.nested, { key: "value" })
225+
assert.strictEqual(script.metadata.simple, "data")
226+
})
122227
})

0 commit comments

Comments
 (0)