@columenlabs/lumen-ai 0.1.277 → 0.1.279

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.
@@ -177,6 +177,8 @@ export interface AskInput {
177
177
  outcomes?: AssistantScreenOutcome[];
178
178
  /** The person kept this message in this chat after a fresh chat was offered (`offerFresh`). */
179
179
  stay?: boolean;
180
+ /** How many images went with the message (set by the agent: the images go in its parts). */
181
+ image_count?: number;
180
182
  }
181
183
  /** A quote as sent, checked: its text cut to `ASSISTANT_QUOTE_MAX`, and whose message it came from. */
182
184
  export declare function quoteOf(q: unknown): AssistantQuote | undefined;
@@ -413,7 +413,12 @@ export class LumenAgent extends Think {
413
413
  this.#steered = [];
414
414
  // The images go in the message's parts; the turn's plan reads the rest.
415
415
  const { images: _images, ...asked } = input;
416
- this.#input = { ...asked, ...(map ? { screen: map } : {}) };
416
+ const pictures = imageParts(input.images);
417
+ this.#input = {
418
+ ...asked,
419
+ ...(map ? { screen: map } : {}),
420
+ ...(pictures.length ? { image_count: pictures.length } : {}),
421
+ };
417
422
  if (!map)
418
423
  delete this.#input.screen;
419
424
  let failed = false;
@@ -424,7 +429,7 @@ export class LumenAgent extends Think {
424
429
  parts: [
425
430
  ...(quote ? [{ type: 'text', text: quoteLead(quote) }] : []),
426
431
  { type: 'text', text: input.message },
427
- ...imageParts(input.images),
432
+ ...pictures,
428
433
  ],
429
434
  metadata: meta,
430
435
  }, {
@@ -63,7 +63,16 @@ export function planFetch(base = fetch, name = 'Assistant') {
63
63
  ];
64
64
  const streamed = body.stream === true;
65
65
  body.stream = true;
66
- const res = await base(input, { ...init, body: JSON.stringify(body) });
66
+ // ChatGPT keeps a plan's prompt cache by the request's `session-id` header, not the body's
67
+ // `prompt_cache_key` (OpenAI's own Codex client: "ChatGPT derives cache affinity from the
68
+ // Responses session-id header", codex-rs/core/src/client.rs). Without it each call of a plan
69
+ // went to a fresh cache and read 0 (live 2026-11-14), while the same calls on a key read 92%.
70
+ const headers = new Headers(init?.headers);
71
+ if (typeof body.prompt_cache_key === 'string' && !headers.has('session-id')) {
72
+ headers.set('session-id', body.prompt_cache_key);
73
+ headers.set('thread-id', body.prompt_cache_key);
74
+ }
75
+ const res = await base(input, { ...init, headers, body: JSON.stringify(body) });
67
76
  // A refusal is logged with OpenAI's own reason; the person sees a plain sentence.
68
77
  if (!res.ok)
69
78
  console.error(JSON.stringify({
@@ -278,6 +278,8 @@ export interface AgentTurnInput {
278
278
  tier?: string;
279
279
  /** The browser's IANA zone, for the bearings. */
280
280
  time_zone?: string;
281
+ /** How many images went with the message, counted in the chat's usage (`UsageEntry.images`). */
282
+ image_count?: number;
281
283
  /** When the chat's last message was, for "since you last asked". */
282
284
  last_at?: number;
283
285
  /**
@@ -549,6 +549,9 @@ export class Assistant {
549
549
  id: `chat:${input.chat}`,
550
550
  kind: 'chat',
551
551
  title: message.split('\n')[0] || 'Chat',
552
+ turns: 1,
553
+ images: input.image_count ?? 0,
554
+ refs: input.refs?.length ?? 0,
552
555
  own_usd: ownKeyUsd(paying, onPayer),
553
556
  own_plan_calls: ownPlanCalls(paying, onPayer),
554
557
  own_plan_usd: ownPlanUsd(paying, onPayer),
@@ -1208,7 +1211,11 @@ export class Assistant {
1208
1211
  ...(plan ? {} : { max_output_tokens: max }),
1209
1212
  ...(effort &&
1210
1213
  effort !== 'default' && { reasoning: { effort: effort === 'max' ? 'high' : effort } }),
1211
- }, signal ? { signal } : {});
1214
+ }, {
1215
+ ...(signal ? { signal } : {}),
1216
+ // A ChatGPT plan keeps its prompt cache by this header, not the body's key (planFetch).
1217
+ ...(cacheKey ? { headers: { 'session-id': cacheKey, 'thread-id': cacheKey } } : {}),
1218
+ });
1212
1219
  for await (const e of stream) {
1213
1220
  if (e.type === 'response.output_text.delta' && typeof e.delta === 'string')
1214
1221
  text += e.delta;
@@ -316,6 +316,11 @@ export declare class State {
316
316
  kind: UsageEntry['kind'];
317
317
  task?: string;
318
318
  title: string;
319
+ /** Messages the person sent with this use (1 a chat turn; a run's calls send none). */
320
+ turns?: number;
321
+ /** Images and picks (refs) that went with those messages. */
322
+ images?: number;
323
+ refs?: number;
319
324
  own_usd?: number;
320
325
  own_plan_calls?: number;
321
326
  own_plan_usd?: number;
@@ -429,6 +429,9 @@ export class State {
429
429
  const own = (prev?.own_usd ?? 0) + (e.own_usd ?? 0);
430
430
  const plan = (prev?.own_plan_calls ?? 0) + (e.own_plan_calls ?? 0);
431
431
  const planUsd = (prev?.own_plan_usd ?? 0) + (e.own_plan_usd ?? 0);
432
+ const turns = (prev?.turns ?? 0) + (e.turns ?? 0);
433
+ const images = (prev?.images ?? 0) + (e.images ?? 0);
434
+ const refs = (prev?.refs ?? 0) + (e.refs ?? 0);
432
435
  entries.unshift({
433
436
  id: e.id,
434
437
  kind: e.kind,
@@ -444,6 +447,9 @@ export class State {
444
447
  ...(own > 0 ? { own_usd: own } : {}),
445
448
  ...(plan > 0 ? { own_plan_calls: plan } : {}),
446
449
  ...(planUsd > 0 ? { own_plan_usd: planUsd } : {}),
450
+ ...(turns > 0 ? { turns } : {}),
451
+ ...(images > 0 ? { images } : {}),
452
+ ...(refs > 0 ? { refs } : {}),
447
453
  parts: merged,
448
454
  });
449
455
  return { entries: entries.slice(0, USAGE_KEPT) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ai",
3
- "version": "0.1.277",
3
+ "version": "0.1.279",
4
4
  "description": "Lumen's assistant and agent parts: a chat assistant's server (Claude tiers, tools over MCP, the web, hand off, spend caps), serving an MCP server from a Worker, read-only tool sets and metered tool calls.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -57,8 +57,8 @@
57
57
  "@anthropic-ai/sdk": "0.133.0",
58
58
  "@cloudflare/computer": "0.8.0",
59
59
  "@cloudflare/think": "0.23.0",
60
- "@columenlabs/lumen-ops": "0.1.277",
61
- "@columenlabs/lumen-ui-types": "0.1.277",
60
+ "@columenlabs/lumen-ops": "0.1.279",
61
+ "@columenlabs/lumen-ui-types": "0.1.279",
62
62
  "agents": "0.32.0",
63
63
  "ai": "7.0.162",
64
64
  "just-bash": "3.6.0",