melchizedek-agents 1.15.1 → 1.15.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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ Consumers of the package read this file; it records changes to the
4
4
  **published API surface** (the exports map in `package.json`, the bins,
5
5
  the starter pack and the templates), not the repo's full history.
6
6
 
7
+ ## 1.15.2 — 2026-11-06
8
+
9
+ **Fixed: a turn's usage and cost left out `generate_image`.** The tool called
10
+ Gemini's image model outside the tracer, so its tokens never reached the
11
+ turn's usage or the ledger's `cost_usd`. The call is now an `llm.request`
12
+ through `traceLlmGeneration`, like every other model call: its tokens count in
13
+ the turn's usage, it counts against `max_steps`, and a cancelled turn stops it
14
+ before the request. Migration 0024 adds the price row for
15
+ `gemini-3.1-flash-image-preview` ($0.50 in, $60.00 out at the image rate,
16
+ $3.00 thinking, from Google's page for `gemini-3.1-flash-image`;
17
+ `verified_at` NULL, since the preview id is not listed). A typical image costs
18
+ about $0.08. Apply it with `npx melchizedek-db apply` before upgrading; without it,
19
+ a turn that made an image prices NULL.
20
+
7
21
  ## 1.15.1 — 2026-11-06
8
22
 
9
23
  **Fixed: a conversation stuck on Anthropic after an approval** (ADR 0155).
@@ -0,0 +1,28 @@
1
+ -- ============================================================================
2
+ -- 0024_image_model_pricing — a price row for the image model generate_image
3
+ -- calls (extends ADR 0136)
4
+ -- ============================================================================
5
+ -- Rows only, in 0017's column layout (USD per million tokens). generate_image
6
+ -- (lib/tools/generateImageTool.ts) now traces its call as an llm.request, so
7
+ -- the turn's usage holds gemini-3.1-flash-image-preview; without a row the
8
+ -- whole turn prices NULL.
9
+ --
10
+ -- gemini-3.1-flash-image-preview: Google's Gemini API pricing page
11
+ -- (aistudio.google.com/docs/pricing, checked 2026-11-06) lists the model as
12
+ -- gemini-3.1-flash-image, standard tier: $0.50 in, $3.00 text and thinking
13
+ -- out, $60.00 image out. The preview id is not on the page, so verified_at
14
+ -- stays NULL (0015's convention). A flat row has one output rate: it holds
15
+ -- the image rate, since nearly all of the call's output is the image, so the
16
+ -- few text tokens beside it are overcounted. Thinking bills at $3.00.
17
+ --
18
+ -- New rows never overwrite a row that exists: an operator's own rates
19
+ -- survive. Nothing secret, nothing personal. Idempotent; safe to re-run.
20
+ -- ============================================================================
21
+
22
+ INSERT INTO model_pricing (model, input_per_mtok, output_per_mtok, thinking_per_mtok, cache_read_per_mtok, cache_write_per_mtok, verified_at, source, note) VALUES
23
+ ('gemini-3.1-flash-image-preview', 0.50, 60.00, 3.00, NULL, NULL, NULL, 'aistudio.google.com/docs/pricing (as gemini-3.1-flash-image), checked 2026-11-06', 'output at the image rate; text output ($3.00) is overcounted; preview id not on the page')
24
+ ON CONFLICT (model) DO NOTHING;
25
+
26
+ INSERT INTO melchizedek_schema_version (version, name)
27
+ VALUES (24, '0024_image_model_pricing')
28
+ ON CONFLICT (version) DO NOTHING;
@@ -14,7 +14,13 @@
14
14
  * The contract is an own Tool (lib/tools/tool.ts); `generateImageTool` is the
15
15
  * own Tool the registry lists, made from it by asTool.
16
16
  */
17
+ import { GoogleGenAI } from '@google/genai';
17
18
  import { z } from 'zod';
19
+ export declare const IMAGE_MODEL = "gemini-3.1-flash-image-preview";
20
+ /** Makes the Gemini client; a test swaps it for a stub. */
21
+ export declare const imageClient: {
22
+ create: (apiKey: string) => Pick<GoogleGenAI, 'models'>;
23
+ };
18
24
  export declare const generateImageContract: import("./toolContract.ts").DefinedTool<z.ZodObject<{
19
25
  prompt: z.ZodString;
20
26
  style: z.ZodOptional<z.ZodString>;
@@ -21,7 +21,12 @@ import { z } from 'zod';
21
21
  import { asTool, defineTool } from './toolContract.js';
22
22
  import { outputsDir } from '../paths.js';
23
23
  import { credentialValue } from '../models/credentialSource.js';
24
- const IMAGE_MODEL = 'gemini-3.1-flash-image-preview';
24
+ import { traceLlmGeneration } from '../observability/tracer.js';
25
+ export const IMAGE_MODEL = 'gemini-3.1-flash-image-preview';
26
+ /** Makes the Gemini client; a test swaps it for a stub. */
27
+ export const imageClient = {
28
+ create: (apiKey) => new GoogleGenAI({ apiKey }),
29
+ };
25
30
  export const generateImageContract = defineTool({
26
31
  name: 'generate_image',
27
32
  description: 'Generates a real image from a structured payload and saves it to the outputs/ directory. ' +
@@ -42,7 +47,7 @@ export const generateImageContract = defineTool({
42
47
  }
43
48
  // Sizes and which options are set, never their values: the prompt carries the person's words.
44
49
  console.log(`[ImageTool] Parameters - prompt ${prompt.length} chars; set: ${[style && 'style', aspect_ratio && 'aspect_ratio', color_palette && 'color_palette'].filter(Boolean).join(', ') || 'none'}`);
45
- const ai = new GoogleGenAI({ apiKey });
50
+ const ai = imageClient.create(apiKey);
46
51
  // Build a rich prompt that bakes in the structured parameters.
47
52
  const parts = [prompt];
48
53
  if (style)
@@ -54,13 +59,28 @@ export const generateImageContract = defineTool({
54
59
  const fullPrompt = parts.join('. ');
55
60
  try {
56
61
  console.log(`[ImageTool] Sending generateContent request to Gemini API (Model: ${IMAGE_MODEL})...`);
57
- const response = await ai.models.generateContent({
58
- model: IMAGE_MODEL,
59
- contents: [{ role: 'user', parts: [{ text: fullPrompt }] }],
60
- config: {
61
- responseModalities: ['IMAGE', 'TEXT'],
62
- },
63
- });
62
+ // Traced as one llm.request like every other model call, so the image
63
+ // model's tokens reach the turn's usage and the ledger prices them
64
+ // (model_pricing); it also counts against the turn's step budget and stops on cancel.
65
+ let response;
66
+ async function* call() {
67
+ response = await ai.models.generateContent({
68
+ model: IMAGE_MODEL,
69
+ contents: [{ role: 'user', parts: [{ text: fullPrompt }] }],
70
+ config: {
71
+ responseModalities: ['IMAGE', 'TEXT'],
72
+ },
73
+ });
74
+ yield { usageMetadata: response.usageMetadata };
75
+ }
76
+ const request = () => ({ model: IMAGE_MODEL, messages: [{ role: 'user', parts: [{ type: 'text', text: fullPrompt }] }] });
77
+ for await (const r of traceLlmGeneration({ provider: 'gemini', model: IMAGE_MODEL, request }, call())) {
78
+ // A refused call (the step budget, a cancelled turn) never reaches the API.
79
+ if (r.errorCode)
80
+ return `[generate_image ERROR] Model: ${IMAGE_MODEL} - ${r.errorMessage || r.errorCode}`;
81
+ }
82
+ if (!response)
83
+ return `No image data returned. Model used: ${IMAGE_MODEL}`;
64
84
  console.log("[ImageTool] Received response from Gemini API.");
65
85
  const candidates = response.candidates ?? [];
66
86
  console.log(`[ImageTool] Candidates found: ${candidates.length}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "melchizedek-agents",
3
- "version": "1.15.1",
3
+ "version": "1.15.2",
4
4
  "description": "A multi-model, multi-agent orchestration framework — YAML-defined syndicates on the engine's own agent loop, Supabase or Postgres session persistence, pgvector long-term memory. Companion repo to the lyceumagents.com curriculum.",
5
5
  "type": "module",
6
6
  "license": "MIT",