@columenlabs/lumen-ai 0.1.184 → 0.1.186

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.
@@ -15,8 +15,11 @@ export interface AiModelOptions {
15
15
  /**
16
16
  * A ChatGPT plan sign-in takes function tools only inside one namespace and no output cap, and a
17
17
  * forced call is the one tool offered with any call required (developers.openai.com/siwc, Preview
18
- * limitations). This fetch rewrites each Responses request to those rules, so the AI SDK's OpenAI
19
- * provider runs on a plan unchanged.
18
+ * limitations). It also keeps nothing (`store: false`, so earlier reasoning rides along encrypted)
19
+ * and takes the system prompt as `instructions`, as the assistant's own OpenAI loop sends it (the
20
+ * AI SDK's request without these came back 400 Bad Request on a plan, 2026-11-04). This fetch
21
+ * rewrites each Responses request to those rules, so the AI SDK's OpenAI provider runs on a plan
22
+ * unchanged.
20
23
  */
21
24
  export declare function planFetch(base?: typeof fetch, name?: string): typeof fetch;
22
25
  /** The AI SDK model a tier runs: its provider's client on the payer's key. */
@@ -8,8 +8,11 @@ import { createOpenAI } from '@ai-sdk/openai';
8
8
  /**
9
9
  * A ChatGPT plan sign-in takes function tools only inside one namespace and no output cap, and a
10
10
  * forced call is the one tool offered with any call required (developers.openai.com/siwc, Preview
11
- * limitations). This fetch rewrites each Responses request to those rules, so the AI SDK's OpenAI
12
- * provider runs on a plan unchanged.
11
+ * limitations). It also keeps nothing (`store: false`, so earlier reasoning rides along encrypted)
12
+ * and takes the system prompt as `instructions`, as the assistant's own OpenAI loop sends it (the
13
+ * AI SDK's request without these came back 400 Bad Request on a plan, 2026-11-04). This fetch
14
+ * rewrites each Responses request to those rules, so the AI SDK's OpenAI provider runs on a plan
15
+ * unchanged.
13
16
  */
14
17
  export function planFetch(base = fetch, name = 'Assistant') {
15
18
  return async (input, init) => {
@@ -17,22 +20,66 @@ export function planFetch(base = fetch, name = 'Assistant') {
17
20
  if (!url.endsWith('/responses') || typeof init?.body !== 'string')
18
21
  return base(input, init);
19
22
  const { max_output_tokens: _cap, ...body } = JSON.parse(init.body);
23
+ body.store = false;
24
+ const include = Array.isArray(body.include) ? body.include : [];
25
+ if (!include.includes('reasoning.encrypted_content'))
26
+ body.include = [...include, 'reasoning.encrypted_content'];
27
+ // System and developer messages become the request's instructions.
28
+ const items = Array.isArray(body.input) ? body.input : [];
29
+ const lead = items.filter((m) => m.role === 'system' || m.role === 'developer');
30
+ if (lead.length) {
31
+ const said = lead.map((m) => textOf(m.content)).filter(Boolean);
32
+ if (typeof body.instructions === 'string' && body.instructions)
33
+ said.unshift(body.instructions);
34
+ body.instructions = said.join('\n\n');
35
+ body.input = items.filter((m) => !lead.includes(m));
36
+ }
20
37
  let tools = body.tools ?? [];
21
38
  const choice = body.tool_choice;
22
39
  if (choice && typeof choice === 'object' && choice.type === 'function' && choice.name) {
23
40
  tools = tools.filter((t) => t.name === choice.name);
24
41
  body.tool_choice = 'required';
25
42
  }
26
- const fns = tools.filter((t) => t.type === 'function').map(({ strict: _, ...t }) => t);
43
+ // Each function as the assistant's own loop sends it: no `strict`, no JSON Schema dialect tag.
44
+ const fns = tools
45
+ .filter((t) => t.type === 'function')
46
+ .map(({ strict: _, ...t }) => {
47
+ const p = t.parameters;
48
+ if (!p || !('$schema' in p))
49
+ return t;
50
+ const { $schema: _s, ...parameters } = p;
51
+ return { ...t, parameters };
52
+ });
27
53
  const rest = tools.filter((t) => t.type !== 'function');
28
54
  if (fns.length)
29
55
  body.tools = [
30
56
  ...rest,
31
57
  { type: 'namespace', name: 'app', description: `${name}'s tools`, tools: fns },
32
58
  ];
33
- return base(input, { ...init, body: JSON.stringify(body) });
59
+ const res = await base(input, { ...init, body: JSON.stringify(body) });
60
+ // A refusal is logged with OpenAI's own reason; the person sees a plain sentence.
61
+ if (!res.ok)
62
+ console.error(JSON.stringify({
63
+ event: 'assistant_plan_refused',
64
+ status: res.status,
65
+ body: (await res.clone().text()).slice(0, 500),
66
+ }));
67
+ return res;
34
68
  };
35
69
  }
70
+ /** A message's text, whether a string or a list of text parts. */
71
+ function textOf(content) {
72
+ if (typeof content === 'string')
73
+ return content;
74
+ if (!Array.isArray(content))
75
+ return '';
76
+ return content
77
+ .map((p) => p && typeof p === 'object' && typeof p.text === 'string'
78
+ ? p.text
79
+ : '')
80
+ .filter(Boolean)
81
+ .join('\n');
82
+ }
36
83
  /** The AI SDK model a tier runs: its provider's client on the payer's key. */
37
84
  export function aiModel(model, key, o = {}) {
38
85
  if (model.provider === 'anthropic')
@@ -43,9 +90,14 @@ export function aiModel(model, key, o = {}) {
43
90
  /** A tier's effort and output cap as a turn's settings; nothing at the model's own default. */
44
91
  export function turnSettings(model, o) {
45
92
  const out = o.plan || !o.out ? {} : { maxOutputTokens: o.out };
46
- if (o.effort === 'default')
47
- return out;
48
- return model.provider === 'anthropic'
49
- ? { ...out, providerOptions: { anthropic: { effort: o.effort } } }
50
- : { ...out, providerOptions: { openai: { reasoningEffort: o.effort } } };
93
+ // A plan keeps nothing, so earlier turns go back as items, not references to stored ones.
94
+ const openai = o.plan ? { store: false } : {};
95
+ if (o.effort !== 'default') {
96
+ if (model.provider === 'anthropic')
97
+ return { ...out, providerOptions: { anthropic: { effort: o.effort } } };
98
+ openai.reasoningEffort = o.effort;
99
+ }
100
+ return model.provider === 'openai' && Object.keys(openai).length
101
+ ? { ...out, providerOptions: { openai } }
102
+ : out;
51
103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ai",
3
- "version": "0.1.184",
3
+ "version": "0.1.186",
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",
@@ -48,8 +48,8 @@
48
48
  "@ai-sdk/openai": "4.0.108",
49
49
  "@anthropic-ai/sdk": "0.133.0",
50
50
  "@cloudflare/think": "0.23.0",
51
- "@columenlabs/lumen-ops": "0.1.184",
52
- "@columenlabs/lumen-ui-types": "0.1.184",
51
+ "@columenlabs/lumen-ops": "0.1.186",
52
+ "@columenlabs/lumen-ui-types": "0.1.186",
53
53
  "agents": "0.32.0",
54
54
  "ai": "7.0.162",
55
55
  "just-bash": "3.6.0",