@promptev/context-engine 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -147,6 +147,29 @@ trusted caller and returns the entire corpus. Use `[]` or `TRUSTED` explicitly.
147
147
  npx context-engine install-skill
148
148
  ```
149
149
 
150
+ ## Compute over tables
151
+
152
+ `engine.compute()` turns the in-scope CSV/XLSX documents into row tables and has
153
+ the LLM write code against them. `computeOverFrames()` is the same path for
154
+ tables you already hold — an uploaded workbook, a connector's sheet, a query
155
+ result — with no document to point at:
156
+
157
+ ```ts
158
+ import { computeOverFrames } from "@promptev/context-engine";
159
+
160
+ const out = await computeOverFrames(
161
+ { expenses: rows }, // { sheetName: Array<Record<string, unknown>> }
162
+ "total the amount column",
163
+ { config: engine.config },
164
+ );
165
+ out.result;
166
+ ```
167
+
168
+ Both are **off by default** (`enableCodeExecution: true` turns them on — only
169
+ behind real OS-level isolation) and both apply `config.redaction` before the LLM
170
+ sees anything: every string cell *and* every column header is masked, and the
171
+ returned object — generated code included — is swept on the way out.
172
+
150
173
  ## Serve
151
174
 
152
175
  ```ts
package/dist/cli.js CHANGED
@@ -414,17 +414,30 @@ var init_config = __esm({
414
414
  "src/config.ts"() {
415
415
  init_redaction();
416
416
  embeddingSchema = z.object({
417
- provider: z.enum(["openai", "azure_openai", "gemini", "voyage", "cohere", "custom"]),
417
+ provider: z.enum(["openai", "azure_openai", "gemini", "vertex_ai", "voyage", "cohere", "custom"]),
418
418
  model: z.string(),
419
419
  dim: z.number().int().positive().nullable().optional().default(null),
420
420
  apiKey: z.string().nullable().optional().default(null),
421
- baseUrl: z.string().nullable().optional().default(null)
421
+ baseUrl: z.string().nullable().optional().default(null),
422
+ // `vertex_ai` only. Left optional on purpose: the Google SDK resolves both
423
+ // from GOOGLE_CLOUD_PROJECT / GOOGLE_CLOUD_LOCATION, which is how a GCP
424
+ // deployment is already wired, and requiring them here would break it.
425
+ //
426
+ // No `.default(null)`, unlike the fields above — these types come from
427
+ // `z.infer`, so a default would make them REQUIRED on the output type and
428
+ // break every hand-written `EmbeddingConfig` literal already compiled
429
+ // against 0.0.1. Optional keeps the addition additive.
430
+ project: z.string().nullable().optional(),
431
+ location: z.string().nullable().optional()
422
432
  });
423
433
  llmSchema = z.object({
424
- provider: z.enum(["anthropic", "openai", "azure_openai", "gemini", "bedrock", "custom"]),
434
+ provider: z.enum(["anthropic", "openai", "azure_openai", "gemini", "vertex_ai", "bedrock", "custom"]),
425
435
  model: z.string(),
426
436
  apiKey: z.string().nullable().optional().default(null),
427
- baseUrl: z.string().nullable().optional().default(null)
437
+ baseUrl: z.string().nullable().optional().default(null),
438
+ // `vertex_ai` only — see the note on embeddingSchema.
439
+ project: z.string().nullable().optional(),
440
+ location: z.string().nullable().optional()
428
441
  });
429
442
  graphSchema = z.object({
430
443
  enabled: z.boolean().default(false),
@@ -2267,6 +2280,47 @@ var init_chunkers = __esm({
2267
2280
  }
2268
2281
  });
2269
2282
 
2283
+ // src/providers/google.ts
2284
+ async function buildGenaiClient(cfg2, timeoutMs, purpose) {
2285
+ const specifier = "@google/genai";
2286
+ let mod;
2287
+ try {
2288
+ mod = await import(specifier);
2289
+ } catch {
2290
+ throw new ExtraMissingError("gemini", specifier, purpose);
2291
+ }
2292
+ const Ctor = mod.GoogleGenAI ?? mod.Client;
2293
+ if (!Ctor) {
2294
+ throw new ExtraMissingError("gemini", specifier, purpose);
2295
+ }
2296
+ const opts = { httpOptions: { timeout: timeoutMs } };
2297
+ if (cfg2.provider === "vertex_ai") {
2298
+ opts.vertexai = true;
2299
+ if (cfg2.project || cfg2.location) {
2300
+ if (cfg2.project) opts.project = cfg2.project;
2301
+ if (cfg2.location) opts.location = cfg2.location;
2302
+ } else if (cfg2.apiKey) {
2303
+ opts.apiKey = cfg2.apiKey;
2304
+ }
2305
+ } else {
2306
+ opts.apiKey = cfg2.apiKey ?? null;
2307
+ }
2308
+ try {
2309
+ return new Ctor(opts);
2310
+ } catch (err) {
2311
+ const message = err instanceof Error ? err.message : String(err);
2312
+ if (!message.includes("Authentication is not set up")) throw err;
2313
+ throw new Error(
2314
+ `${message} Set \`project\` on the provider config, or export GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION. Credentials themselves come from Application Default Credentials.`
2315
+ );
2316
+ }
2317
+ }
2318
+ var init_google = __esm({
2319
+ "src/providers/google.ts"() {
2320
+ init_errors();
2321
+ }
2322
+ });
2323
+
2270
2324
  // src/providers/llm.ts
2271
2325
  var llm_exports = {};
2272
2326
  __export(llm_exports, {
@@ -2302,20 +2356,6 @@ function buildOpenAIChatClient(cfg2) {
2302
2356
  maxRetries: 0
2303
2357
  });
2304
2358
  }
2305
- async function loadGeminiChatClient(apiKey) {
2306
- const specifier = "@google/genai";
2307
- let mod;
2308
- try {
2309
- mod = await import(specifier);
2310
- } catch {
2311
- throw new ExtraMissingError("gemini", specifier, "gemini llm");
2312
- }
2313
- const Ctor = mod.GoogleGenAI ?? mod.Client;
2314
- if (!Ctor) {
2315
- throw new ExtraMissingError("gemini", specifier, "gemini llm");
2316
- }
2317
- return new Ctor({ apiKey: apiKey ?? null, httpOptions: { timeout: GEMINI_CALL_TIMEOUT_MS } });
2318
- }
2319
2359
  async function loadBedrockSdk() {
2320
2360
  const specifier = "@aws-sdk/client-bedrock-runtime";
2321
2361
  try {
@@ -2334,7 +2374,7 @@ function buildLlmClient(cfg2, opts) {
2334
2374
  if (OPENAI_FAMILY.has(cfg2.provider)) {
2335
2375
  return new LLMClient(cfg2, { client: buildOpenAIChatClient(cfg2) });
2336
2376
  }
2337
- if (cfg2.provider === "gemini" || cfg2.provider === "bedrock") {
2377
+ if (GOOGLE_FAMILY.has(cfg2.provider) || cfg2.provider === "bedrock") {
2338
2378
  return new LLMClient(cfg2);
2339
2379
  }
2340
2380
  throw new Error(`unknown llm provider: ${JSON.stringify(cfg2.provider)}`);
@@ -2351,16 +2391,18 @@ async function callLlm(cfg2, opts) {
2351
2391
  await owned.aclose();
2352
2392
  }
2353
2393
  }
2354
- var CALL_TIMEOUT_MS, TIMEOUT_MS, GEMINI_CALL_TIMEOUT_MS, ANTHROPIC_VERSION, ANTHROPIC_MAX_TOKENS, OPENAI_FAMILY, LLMClient;
2394
+ var CALL_TIMEOUT_MS, TIMEOUT_MS, GEMINI_CALL_TIMEOUT_MS, ANTHROPIC_VERSION, ANTHROPIC_MAX_TOKENS, OPENAI_FAMILY, GOOGLE_FAMILY, LLMClient;
2355
2395
  var init_llm = __esm({
2356
2396
  "src/providers/llm.ts"() {
2357
2397
  init_errors();
2398
+ init_google();
2358
2399
  CALL_TIMEOUT_MS = 24e4;
2359
2400
  TIMEOUT_MS = CALL_TIMEOUT_MS;
2360
2401
  GEMINI_CALL_TIMEOUT_MS = CALL_TIMEOUT_MS;
2361
2402
  ANTHROPIC_VERSION = "2023-06-01";
2362
2403
  ANTHROPIC_MAX_TOKENS = 4096;
2363
2404
  OPENAI_FAMILY = /* @__PURE__ */ new Set(["openai", "azure_openai", "custom"]);
2405
+ GOOGLE_FAMILY = /* @__PURE__ */ new Set(["gemini", "vertex_ai"]);
2364
2406
  LLMClient = class {
2365
2407
  cfg;
2366
2408
  provider;
@@ -2400,7 +2442,7 @@ var init_llm = __esm({
2400
2442
  if (OPENAI_FAMILY.has(this.provider)) {
2401
2443
  return this.callOpenAI(system, user, jsonMode, images, maxTokens, temperature);
2402
2444
  }
2403
- if (this.provider === "gemini") {
2445
+ if (GOOGLE_FAMILY.has(this.provider)) {
2404
2446
  return this.callGemini(system, user, jsonMode, images, maxTokens, thinkingBudget, temperature);
2405
2447
  }
2406
2448
  if (this.provider === "bedrock") {
@@ -2483,7 +2525,11 @@ Respond with valid JSON only.`;
2483
2525
  }
2484
2526
  async callGemini(system, user, jsonMode, images, maxTokens, thinkingBudget = null, temperature = null) {
2485
2527
  if (!this.genaiClient) {
2486
- this.genaiClient = await loadGeminiChatClient(this.cfg.apiKey);
2528
+ this.genaiClient = await buildGenaiClient(
2529
+ this.cfg,
2530
+ GEMINI_CALL_TIMEOUT_MS,
2531
+ "gemini llm"
2532
+ );
2487
2533
  }
2488
2534
  const parts = [];
2489
2535
  for (const img of images ?? []) {
@@ -6399,20 +6445,8 @@ function parseSpreadsheetText(dfd, text) {
6399
6445
  return out;
6400
6446
  }
6401
6447
  async function compute(instruction, opts) {
6402
- if (!opts.config.enableCodeExecution) {
6403
- throw new EngineActionError(
6404
- "compute() executes generated code and is disabled by default; set enableCodeExecution=true only in a deployment with out-of-process/container isolation."
6405
- );
6406
- }
6407
- const llmCfg = opts.modelCfg ?? opts.config.llm;
6408
- if (llmCfg == null) {
6409
- throw new Error("compute() requires an LLM: pass modelCfg= or configure ContextEngineConfig.llm");
6410
- }
6411
- if (!instruction?.trim()) {
6412
- throw new EngineActionError("instruction must not be empty");
6413
- }
6448
+ checkComputePreconditions(opts.config, opts.modelCfg, instruction);
6414
6449
  const dfd = await requireDanfo();
6415
- const timeout = Math.max(1, Math.min(Math.trunc(opts.timeout || DEFAULT_COMPUTE_TIMEOUT), 300));
6416
6450
  const principals = opts.principals ?? null;
6417
6451
  const params = [];
6418
6452
  let where = scopeSql(opts.sourceIds ?? null, principals, params);
@@ -6482,6 +6516,69 @@ async function compute(instruction, opts) {
6482
6516
  if (!Object.keys(dfs).length) {
6483
6517
  throw new EngineActionError("in-scope documents did not parse into any usable dataframe");
6484
6518
  }
6519
+ return computeOverFrames(dfs, instruction, {
6520
+ config: opts.config,
6521
+ modelCfg: opts.modelCfg,
6522
+ timeout: opts.timeout,
6523
+ hooks: opts.hooks,
6524
+ principals,
6525
+ documents
6526
+ });
6527
+ }
6528
+ function checkComputePreconditions(config, modelCfg, instruction) {
6529
+ if (!config.enableCodeExecution) {
6530
+ throw new EngineActionError(
6531
+ "compute() executes generated code and is disabled by default; set enableCodeExecution=true only in a deployment with out-of-process/container isolation."
6532
+ );
6533
+ }
6534
+ const llmCfg = modelCfg ?? config.llm;
6535
+ if (llmCfg == null) {
6536
+ throw new Error("compute() requires an LLM: pass modelCfg= or configure ContextEngineConfig.llm");
6537
+ }
6538
+ if (!instruction?.trim()) {
6539
+ throw new EngineActionError("instruction must not be empty");
6540
+ }
6541
+ return llmCfg;
6542
+ }
6543
+ function maskFrames(frames, policy, opts) {
6544
+ if (policy == null || policy.isEmpty()) return frames;
6545
+ const mask = (text) => redactValueRecursive(text, policy, opts);
6546
+ const masked = {};
6547
+ for (const [name, rows] of Object.entries(frames)) {
6548
+ const labels = /* @__PURE__ */ new Map();
6549
+ const taken = /* @__PURE__ */ new Set();
6550
+ for (const row of rows) {
6551
+ for (const column of Object.keys(row)) {
6552
+ if (labels.has(column)) continue;
6553
+ const base = mask(column);
6554
+ let label = base;
6555
+ for (let n = 2; taken.has(label); n++) label = `${base}_${n}`;
6556
+ taken.add(label);
6557
+ labels.set(column, label);
6558
+ }
6559
+ }
6560
+ masked[name] = rows.map(
6561
+ (row) => Object.fromEntries(
6562
+ Object.entries(row).map(([column, value]) => [
6563
+ labels.get(column) ?? column,
6564
+ typeof value === "string" ? mask(value) : value
6565
+ ])
6566
+ )
6567
+ );
6568
+ }
6569
+ return masked;
6570
+ }
6571
+ async function computeOverFrames(frames, instruction, opts) {
6572
+ const llmCfg = checkComputePreconditions(opts.config, opts.modelCfg, instruction);
6573
+ if (!frames || !Object.keys(frames).length) {
6574
+ throw new EngineActionError("no tabular data to compute over");
6575
+ }
6576
+ const hooks = opts.hooks ?? {};
6577
+ const principals = opts.principals ?? null;
6578
+ const documents = [...opts.documents ?? []];
6579
+ const timeout = Math.max(1, Math.min(Math.trunc(opts.timeout || DEFAULT_COMPUTE_TIMEOUT), 300));
6580
+ const redactOpts = { principals, secretKey: opts.config.secretKey, hooks };
6581
+ const dfs = maskFrames(frames, opts.config.redaction, redactOpts);
6485
6582
  const schemaLines = Object.entries(dfs).map(
6486
6583
  ([name, table]) => `- ${name}: columns=${JSON.stringify(Object.keys(table[0] ?? {}))}, rows=${table.length}`
6487
6584
  );
@@ -6498,7 +6595,7 @@ ${schemaLines.join("\n")}`;
6498
6595
  jsonMode: false
6499
6596
  });
6500
6597
  } catch (exc) {
6501
- emitError(opts.hooks, exc, { stage: "compute_codegen", instruction: instruction.slice(0, 200) });
6598
+ emitError(hooks, exc, { stage: "compute_codegen", instruction: instruction.slice(0, 200) });
6502
6599
  throw exc;
6503
6600
  }
6504
6601
  const code = stripCodeFences(rawCode);
@@ -6509,12 +6606,12 @@ ${schemaLines.join("\n")}`;
6509
6606
  maskedCode = redactValueRecursive(code.slice(0, 500), opts.config.redaction, {
6510
6607
  principals,
6511
6608
  secretKey: opts.config.secretKey,
6512
- hooks: opts.hooks
6609
+ hooks
6513
6610
  });
6514
6611
  } catch {
6515
6612
  maskedCode = "<redaction failed: code omitted>";
6516
6613
  }
6517
- emitError(opts.hooks, new Error(execResult.error || "compute execution failed"), {
6614
+ emitError(hooks, new Error(execResult.error || "compute execution failed"), {
6518
6615
  stage: "compute_exec",
6519
6616
  code: maskedCode
6520
6617
  });
@@ -6535,7 +6632,7 @@ ${schemaLines.join("\n")}`;
6535
6632
  return redactValueRecursive(result, opts.config.redaction, {
6536
6633
  principals,
6537
6634
  secretKey: opts.config.secretKey,
6538
- hooks: opts.hooks
6635
+ hooks
6539
6636
  });
6540
6637
  }
6541
6638
  var MAX_LIST_LIMIT, MAX_COMPUTE_TEXT_CHARS, DEFAULT_COMPUTE_TIMEOUT, MAX_COMPUTE_DOCUMENTS, TABULAR_MIME_PATTERNS, UUID_RE3, WORD_RE, STOPWORDS, SHEET_MARKER_RE2, CODE_FENCE_RE, COMPUTE_SYSTEM_PROMPT, visible;
@@ -6661,20 +6758,6 @@ function buildOpenAIClient(cfg2) {
6661
6758
  maxRetries: 0
6662
6759
  });
6663
6760
  }
6664
- async function loadGeminiEmbedClient(apiKey) {
6665
- const specifier = "@google/genai";
6666
- let mod;
6667
- try {
6668
- mod = await import(specifier);
6669
- } catch {
6670
- throw new ExtraMissingError("gemini", specifier, "gemini embeddings");
6671
- }
6672
- const Ctor = mod.GoogleGenAI ?? mod.Client;
6673
- if (!Ctor) {
6674
- throw new ExtraMissingError("gemini", specifier, "gemini embeddings");
6675
- }
6676
- return new Ctor({ apiKey: apiKey ?? null, httpOptions: { timeout: TIMEOUT_MS3 } });
6677
- }
6678
6761
  function buildEmbedder(cfg2, opts) {
6679
6762
  if (OPENAI_FAMILY2.has(cfg2.provider)) {
6680
6763
  return new Embedder(cfg2, { client: buildOpenAIClient(cfg2) });
@@ -6682,18 +6765,19 @@ function buildEmbedder(cfg2, opts) {
6682
6765
  if (cfg2.provider === "voyage" || cfg2.provider === "cohere") {
6683
6766
  return new Embedder(cfg2, { fetch: globalThis.fetch });
6684
6767
  }
6685
- if (cfg2.provider === "gemini") {
6768
+ if (GOOGLE_FAMILY2.has(cfg2.provider)) {
6686
6769
  return new Embedder(cfg2);
6687
6770
  }
6688
6771
  throw new Error(`unknown embedding provider: ${JSON.stringify(cfg2.provider)}`);
6689
6772
  }
6690
- var TIMEOUT_MS3, OPENAI_FAMILY2, Embedder;
6773
+ var TIMEOUT_MS3, OPENAI_FAMILY2, GOOGLE_FAMILY2, Embedder;
6691
6774
  var init_embeddings = __esm({
6692
6775
  "src/providers/embeddings.ts"() {
6693
- init_errors();
6694
6776
  init_text();
6777
+ init_google();
6695
6778
  TIMEOUT_MS3 = 3e4;
6696
6779
  OPENAI_FAMILY2 = /* @__PURE__ */ new Set(["openai", "azure_openai", "custom"]);
6780
+ GOOGLE_FAMILY2 = /* @__PURE__ */ new Set(["gemini", "vertex_ai"]);
6697
6781
  Embedder = class {
6698
6782
  cfg;
6699
6783
  provider;
@@ -6741,7 +6825,7 @@ var init_embeddings = __esm({
6741
6825
  /** Provider dispatch. Overridable per-instance (tests stub this). */
6742
6826
  async rawEmbed(texts, kind = "document") {
6743
6827
  if (OPENAI_FAMILY2.has(this.provider)) return this.embedOpenAI(texts);
6744
- if (this.provider === "gemini") return this.embedGemini(texts);
6828
+ if (GOOGLE_FAMILY2.has(this.provider)) return this.embedGemini(texts);
6745
6829
  if (this.provider === "voyage") return this.embedVoyage(texts, kind);
6746
6830
  if (this.provider === "cohere") return this.embedCohere(texts, kind);
6747
6831
  throw new Error(`unknown embedding provider: ${JSON.stringify(this.provider)}`);
@@ -6757,7 +6841,7 @@ var init_embeddings = __esm({
6757
6841
  }
6758
6842
  async embedGemini(texts) {
6759
6843
  if (!this.genaiClient) {
6760
- this.genaiClient = await loadGeminiEmbedClient(this.cfg.apiKey);
6844
+ this.genaiClient = await buildGenaiClient(this.cfg, TIMEOUT_MS3, "gemini embeddings");
6761
6845
  }
6762
6846
  const resp = await this.genaiClient.models.embedContent({
6763
6847
  model: this.model,