@columenlabs/lumen-ai 0.1.109 → 0.1.111
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/dist/assistant/engine.js +31 -38
- package/package.json +3 -3
package/dist/assistant/engine.js
CHANGED
|
@@ -155,11 +155,11 @@ function openaiStreamError(code, plan) {
|
|
|
155
155
|
return 'OpenAI declined to answer this message.';
|
|
156
156
|
return `OpenAI could not answer (${code || 'no reason given'}). It is logged; try again or pick another model.`;
|
|
157
157
|
}
|
|
158
|
-
/**
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
/** A tool's schema without JSON Schema's `$schema` line, which zod writes and no provider needs. */
|
|
159
|
+
const withoutMeta = (schema) => {
|
|
160
|
+
const { $schema: _, ...rest } = schema ?? {};
|
|
161
|
+
return rest;
|
|
162
|
+
};
|
|
163
163
|
/** Whether a response's output has words or a call to show. */
|
|
164
164
|
const shows = (output) => output.some((b) => b.type === 'function_call' ||
|
|
165
165
|
(b.type === 'message' && b.content.some((c) => c.type === 'output_text' && c.text)));
|
|
@@ -564,7 +564,10 @@ export class Assistant {
|
|
|
564
564
|
return [...fns.entries()].map(([name, f]) => ({
|
|
565
565
|
name,
|
|
566
566
|
description: `${f.draft ? "DRAFT ONLY: this call changes nothing. It puts a card on the person's screen that they Save or Dismiss. " : f.write ? 'WRITES AT ONCE: this call makes the change now; the person can undo it. ' : ''}${f.def.title ? `${f.def.title}. ` : ''}${f.def.description ?? ''}`.slice(0, 4000),
|
|
567
|
-
input_schema: {
|
|
567
|
+
input_schema: {
|
|
568
|
+
type: 'object',
|
|
569
|
+
...withoutMeta(f.def.inputSchema),
|
|
570
|
+
},
|
|
568
571
|
}));
|
|
569
572
|
}
|
|
570
573
|
// ---------------------------------------------------------------- the turn
|
|
@@ -1115,11 +1118,11 @@ export class Assistant {
|
|
|
1115
1118
|
.filter((m) => m.role !== 'assistant' || m.content.replace('…(cut)', '').trim() !== '');
|
|
1116
1119
|
let answer = '';
|
|
1117
1120
|
let force = o.first?.type === 'tool' ? { type: 'function', name: o.first.name } : undefined;
|
|
1118
|
-
// A plan sign-in takes function tools only inside a namespace
|
|
1119
|
-
//
|
|
1120
|
-
const sent = (only
|
|
1121
|
+
// A plan sign-in takes function tools only inside a namespace, and no output cap
|
|
1122
|
+
// (`max_output_tokens` is unsupported there: developers.openai.com/siwc, Preview limitations).
|
|
1123
|
+
const sent = (only) => {
|
|
1121
1124
|
const fns = only ? fnTools.filter((t) => t.name === only) : fnTools;
|
|
1122
|
-
const out =
|
|
1125
|
+
const out = plan ? {} : { max_output_tokens: session.tier.out ?? DEFAULT_OUT };
|
|
1123
1126
|
if (!plan)
|
|
1124
1127
|
return { tools: fns, ...out };
|
|
1125
1128
|
return {
|
|
@@ -1164,20 +1167,20 @@ export class Assistant {
|
|
|
1164
1167
|
}));
|
|
1165
1168
|
return new AssistantError(openaiStreamError(er.code || er.type || '', plan), 'upstream');
|
|
1166
1169
|
};
|
|
1167
|
-
// One call of this round; asked again (fresh stream state) when
|
|
1168
|
-
//
|
|
1169
|
-
const ask = (
|
|
1170
|
+
// One call of this round; asked again (fresh stream state) when it spends its whole output
|
|
1171
|
+
// on reasoning.
|
|
1172
|
+
const ask = (eff) => {
|
|
1170
1173
|
partial = '';
|
|
1171
1174
|
seen = {};
|
|
1172
1175
|
items = [];
|
|
1173
|
-
return askOnce(
|
|
1176
|
+
return askOnce(eff);
|
|
1174
1177
|
};
|
|
1175
|
-
const askOnce = (
|
|
1178
|
+
const askOnce = (eff) => (async () => {
|
|
1176
1179
|
const stream = await client.responses.create({
|
|
1177
1180
|
model: session.model.id,
|
|
1178
1181
|
instructions,
|
|
1179
1182
|
input,
|
|
1180
|
-
...sent(only
|
|
1183
|
+
...sent(only),
|
|
1181
1184
|
store: false,
|
|
1182
1185
|
stream: true,
|
|
1183
1186
|
include: ['reasoning.encrypted_content'],
|
|
@@ -1220,24 +1223,13 @@ export class Assistant {
|
|
|
1220
1223
|
}
|
|
1221
1224
|
throw err;
|
|
1222
1225
|
});
|
|
1223
|
-
let
|
|
1224
|
-
let res;
|
|
1225
|
-
try {
|
|
1226
|
-
res = await ask(cap, effort);
|
|
1227
|
-
}
|
|
1228
|
-
catch (err) {
|
|
1229
|
-
// A plan backend that refuses an output cap keeps its own: ask once more without it.
|
|
1230
|
-
if (!(plan && cap && refusesCap(err)))
|
|
1231
|
-
throw err;
|
|
1232
|
-
planCapRefused = true;
|
|
1233
|
-
cap = false;
|
|
1234
|
-
console.error(JSON.stringify({ event: 'assistant_openai_cap_refused', model: session.model.id }));
|
|
1235
|
-
res = await ask(cap, effort);
|
|
1236
|
-
}
|
|
1226
|
+
let res = await ask(effort);
|
|
1237
1227
|
// A reasoning model can spend the whole output on thinking and say nothing: ask once more
|
|
1238
|
-
// with less thinking before giving up.
|
|
1228
|
+
// with less thinking before giving up. One that stopped before writing anything is not asked
|
|
1229
|
+
// again: less thinking cannot help it.
|
|
1239
1230
|
if (res.status === 'incomplete' &&
|
|
1240
1231
|
res.incomplete_details?.reason === 'max_output_tokens' &&
|
|
1232
|
+
(res.usage?.output_tokens ?? 0) > 0 &&
|
|
1241
1233
|
!partial &&
|
|
1242
1234
|
!shows(res.output?.length ? res.output : items) &&
|
|
1243
1235
|
effort !== 'low') {
|
|
@@ -1245,12 +1237,11 @@ export class Assistant {
|
|
|
1245
1237
|
event: 'assistant_openai_retry',
|
|
1246
1238
|
model: session.model.id,
|
|
1247
1239
|
plan,
|
|
1248
|
-
cap,
|
|
1249
1240
|
why: 'no_words',
|
|
1250
1241
|
usage: res.usage ?? null,
|
|
1251
1242
|
events: seen,
|
|
1252
1243
|
}));
|
|
1253
|
-
res = await ask(
|
|
1244
|
+
res = await ask('low');
|
|
1254
1245
|
}
|
|
1255
1246
|
if (res.status === 'failed') {
|
|
1256
1247
|
if (partial)
|
|
@@ -1301,11 +1292,13 @@ export class Assistant {
|
|
|
1301
1292
|
parts: output.flatMap((b) => b.type === 'message' ? b.content.map((c) => c.type) : []),
|
|
1302
1293
|
}));
|
|
1303
1294
|
if (!answer)
|
|
1304
|
-
throw new AssistantError(res.incomplete_details?.reason === 'max_output_tokens'
|
|
1305
|
-
? 'OpenAI
|
|
1306
|
-
: res.incomplete_details?.reason
|
|
1307
|
-
?
|
|
1308
|
-
:
|
|
1295
|
+
throw new AssistantError(res.incomplete_details?.reason === 'max_output_tokens' && !res.usage?.output_tokens
|
|
1296
|
+
? 'OpenAI stopped before reading the message. Try again, or pick another model for this tier.'
|
|
1297
|
+
: res.incomplete_details?.reason === 'max_output_tokens'
|
|
1298
|
+
? 'OpenAI spent the whole answer thinking and wrote nothing. Try again, or pick a smaller tier.'
|
|
1299
|
+
: res.incomplete_details?.reason
|
|
1300
|
+
? `OpenAI stopped before answering (${res.incomplete_details.reason}).`
|
|
1301
|
+
: 'OpenAI sent no answer.', 'upstream');
|
|
1309
1302
|
}
|
|
1310
1303
|
const cut = res.incomplete_details?.reason === 'max_output_tokens' && (words || hasCall)
|
|
1311
1304
|
? ' …(cut)'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.111",
|
|
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",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@anthropic-ai/sdk": "0.133.0",
|
|
44
|
-
"@columenlabs/lumen-ops": "0.1.
|
|
45
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
44
|
+
"@columenlabs/lumen-ops": "0.1.111",
|
|
45
|
+
"@columenlabs/lumen-ui-types": "0.1.111",
|
|
46
46
|
"openai": "7.32.0",
|
|
47
47
|
"zod": "^4.3.6"
|
|
48
48
|
},
|