@lampdotdev/lamp 0.36.2-nightly.25 → 0.36.2-nightly.26
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/Resources/skills/web-app/SKILL.md +1 -1
- package/cli/app-ai.mjs +2 -2
- package/cli/lamps/lamp-for-games.js +32 -6
- package/cli/lamps/lamp.js +32 -6
- package/cli/lamps/mac-starter/.lamp/meta.json +1 -1
- package/cli/lamps/mac-starter/Sources/LampSupport.swift +24 -6
- package/cli/lamps/web-starter/lamp.js +32 -6
- package/cli/local-ai.mjs +1 -1
- package/package.json +1 -1
|
@@ -75,7 +75,7 @@ needs Lamp (for AI, `LampAI.status`'s `reason`), never an error or an empty scre
|
|
|
75
75
|
|
|
76
76
|
`LampAI.act(request, { about, context, tools, history, onStep })` runs a short loop: the AI picks calls among the
|
|
77
77
|
app's tools, the page runs them, the AI sees the results, until it replies (at most 4 AI calls and 6 writes a
|
|
78
|
-
turn, and no time limit unless the app sets one with `limits: { seconds }`, for a feature that should answer quickly). It works with
|
|
78
|
+
turn, and no time limit unless the app sets one with `limits: { seconds }`, for a feature that should answer quickly; each read gives the AI up to 32 KB of its result, and an app whose state is bigger raises it with `limits: { result }`, up to 256 KB, rather than having the AI page through it). It works with
|
|
79
79
|
whichever AI the person set up for apps' text, so don't tie it to voice: typing works wherever `act` does. Its comment in lamp.js has the shapes. This is the one recipe for it:
|
|
80
80
|
|
|
81
81
|
- Show the feature where `LampAI.status('act')` says it's available, and keep the app's own buttons for everything:
|
package/cli/app-ai.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// (OpenRouter: `chatBody`, `readMessage`), or one prompt whose answer must fit the step's JSON shape (Claude Code, Codex:
|
|
15
15
|
// `planPrompt`, `planSchema`, `readPlan`).
|
|
16
16
|
|
|
17
|
-
export const MAX_TOOLS = 24, MAX_CALLS = 8, MAX_STEPS = 4, MAX_BODY =
|
|
17
|
+
export const MAX_TOOLS = 24, MAX_CALLS = 8, MAX_STEPS = 4, MAX_BODY = 2 * 1024 * 1024, MAX_CHOICES = 4, MAX_CHOICE = 24;
|
|
18
18
|
/** The longest a step may take, whatever its route, before its call is taken to have stopped answering (`ACT_STEP_SECONDS`
|
|
19
19
|
* in lamp.js, `AppAct.turnSeconds`). A turn has no time of its own unless the app gives it one. */
|
|
20
20
|
export const MAX_TURN_SECONDS = 600;
|
|
@@ -66,7 +66,7 @@ function parseArg(raw, path) {
|
|
|
66
66
|
* steps, route, seconds, final}`, each tool's `args` as [name, shape] pairs by name. Throws `Refused`.
|
|
67
67
|
*/
|
|
68
68
|
export function readStep(text) {
|
|
69
|
-
if (Buffer.byteLength(text) > MAX_BODY) throw new Refused(413, "A step can carry at most
|
|
69
|
+
if (Buffer.byteLength(text) > MAX_BODY) throw new Refused(413, "A step can carry at most 2 MB.");
|
|
70
70
|
let body;
|
|
71
71
|
try { body = JSON.parse(text); } catch {}
|
|
72
72
|
if (!body || typeof body !== "object" || Array.isArray(body)) throw new Refused(400, "A step needs a JSON object.");
|
|
@@ -521,16 +521,33 @@
|
|
|
521
521
|
// given ACT_STEP_SECONDS, which only a call that has stopped answering reaches. An app that wants a quicker turn
|
|
522
522
|
// asks for one (`limits: { seconds }`); steps and writes it may only lower.
|
|
523
523
|
var ACT_LIMITS = { steps: 4, writes: 6 };
|
|
524
|
+
// How much of one read goes back to the model: room for a real app's state (a 184-piece model, a week's calendar)
|
|
525
|
+
// in one step, since paging through it costs steps a turn needs for its work. An app with more raises it
|
|
526
|
+
// (`limits: { result }`), up to ACT_RESULT_MAX.
|
|
527
|
+
var ACT_RESULT = 32768, ACT_RESULT_MAX = 262144;
|
|
528
|
+
// What a turn's reads give back in all, which every later step carries again: kept under what a step may carry
|
|
529
|
+
// (`MAX_BODY` in lamp's app-ai.mjs), with room for the request, the tools and the steps' own words.
|
|
530
|
+
var ACT_TURN_RESULTS = 1200000;
|
|
524
531
|
var ACT_STEP_SECONDS = 600;
|
|
525
532
|
var actRan = {}; // turn id -> call key -> the result a write gave, while this copy of the page is open
|
|
526
533
|
function actId() { return 't' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8); }
|
|
527
534
|
function isList(v) { return Object.prototype.toString.call(v) === '[object Array]'; }
|
|
528
|
-
/**
|
|
529
|
-
function
|
|
535
|
+
/** A text's size in UTF-8 bytes, as a step is measured where it's checked. */
|
|
536
|
+
function utf8Bytes(text) { try { return unescape(encodeURIComponent(text)).length; } catch (e) { return text.length * 3; } }
|
|
537
|
+
/** At most `max` bytes of a result go back to the model, as it's sent (UTF-8, its cut wrapper included), cut with a note. */
|
|
538
|
+
function actValue(value, max) {
|
|
530
539
|
var json = jsonState(value === undefined ? null : value), text;
|
|
531
540
|
try { text = JSON.stringify(json); } catch (e) { return { note: 'The result couldn\'t be read.' }; }
|
|
532
|
-
if (text
|
|
533
|
-
|
|
541
|
+
if (!text || utf8Bytes(text) <= max) return json;
|
|
542
|
+
// The cut is JSON inside JSON, so its quotes are escaped again: shrink it until the whole wrapper fits.
|
|
543
|
+
var cut = text.slice(0, max);
|
|
544
|
+
for (var i = 0; i < 24; i++) {
|
|
545
|
+
var wrapped = { cut: cut, note: 'Cut short: ask for less.' };
|
|
546
|
+
var size = utf8Bytes(JSON.stringify(wrapped));
|
|
547
|
+
if (size <= max) return wrapped;
|
|
548
|
+
cut = cut.slice(0, Math.max(0, Math.floor(cut.length * max / size) - 16));
|
|
549
|
+
}
|
|
550
|
+
return { note: 'Cut short: ask for less.' };
|
|
534
551
|
}
|
|
535
552
|
/** The page's own check of a call before it runs: a tool it declared, its arguments an object with nothing
|
|
536
553
|
* undeclared and nothing required missing. Lamp has checked the shapes; this is what the page itself can hold to. */
|
|
@@ -552,6 +569,15 @@
|
|
|
552
569
|
limits[k] = Math.max(1, Math.min(asked, ACT_LIMITS[k]));
|
|
553
570
|
});
|
|
554
571
|
var asked = o.limits && typeof o.limits.seconds === 'number' && o.limits.seconds > 0 ? o.limits.seconds : null;
|
|
572
|
+
var resultMax = o.limits && typeof o.limits.result === 'number' && o.limits.result > 0
|
|
573
|
+
? Math.min(Math.max(1024, Math.floor(o.limits.result)), ACT_RESULT_MAX) : ACT_RESULT;
|
|
574
|
+
// Each result takes from the turn's allowance, so later reads get less rather than the step growing past it.
|
|
575
|
+
var resultsLeft = ACT_TURN_RESULTS;
|
|
576
|
+
function sized(value) {
|
|
577
|
+
var kept = actValue(value, Math.max(1024, Math.min(resultMax, resultsLeft)));
|
|
578
|
+
try { resultsLeft = Math.max(0, resultsLeft - utf8Bytes(JSON.stringify(kept))); } catch (e) {}
|
|
579
|
+
return kept;
|
|
580
|
+
}
|
|
555
581
|
var id = o.turn || actId();
|
|
556
582
|
if (!actRan[id]) {
|
|
557
583
|
var kept = Object.keys(actRan);
|
|
@@ -601,7 +627,7 @@
|
|
|
601
627
|
}).then(function (value) {
|
|
602
628
|
if (value === NOT_RUN) return;
|
|
603
629
|
out.reads.push({ name: c.name, args: c.args });
|
|
604
|
-
results[c.id] = { id: c.id, ok: true, value:
|
|
630
|
+
results[c.id] = { id: c.id, ok: true, value: sized(value) };
|
|
605
631
|
}, function (e) { results[c.id] = { id: c.id, ok: false, error: refused(e) }; });
|
|
606
632
|
})).then(function () {
|
|
607
633
|
var stopped = null;
|
|
@@ -638,7 +664,7 @@
|
|
|
638
664
|
var running;
|
|
639
665
|
try { running = Promise.resolve(tool.run(c.args, call)); } catch (e) { running = Promise.reject(e); }
|
|
640
666
|
return running.then(function (value) {
|
|
641
|
-
var kept =
|
|
667
|
+
var kept = sized(value);
|
|
642
668
|
ran[key] = kept;
|
|
643
669
|
var entry = { name: c.name, args: c.args, result: value };
|
|
644
670
|
if (value && typeof value === 'object' && value.partly) entry.partly = String(value.partly);
|
package/cli/lamps/lamp.js
CHANGED
|
@@ -521,16 +521,33 @@
|
|
|
521
521
|
// given ACT_STEP_SECONDS, which only a call that has stopped answering reaches. An app that wants a quicker turn
|
|
522
522
|
// asks for one (`limits: { seconds }`); steps and writes it may only lower.
|
|
523
523
|
var ACT_LIMITS = { steps: 4, writes: 6 };
|
|
524
|
+
// How much of one read goes back to the model: room for a real app's state (a 184-piece model, a week's calendar)
|
|
525
|
+
// in one step, since paging through it costs steps a turn needs for its work. An app with more raises it
|
|
526
|
+
// (`limits: { result }`), up to ACT_RESULT_MAX.
|
|
527
|
+
var ACT_RESULT = 32768, ACT_RESULT_MAX = 262144;
|
|
528
|
+
// What a turn's reads give back in all, which every later step carries again: kept under what a step may carry
|
|
529
|
+
// (`MAX_BODY` in lamp's app-ai.mjs), with room for the request, the tools and the steps' own words.
|
|
530
|
+
var ACT_TURN_RESULTS = 1200000;
|
|
524
531
|
var ACT_STEP_SECONDS = 600;
|
|
525
532
|
var actRan = {}; // turn id -> call key -> the result a write gave, while this copy of the page is open
|
|
526
533
|
function actId() { return 't' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8); }
|
|
527
534
|
function isList(v) { return Object.prototype.toString.call(v) === '[object Array]'; }
|
|
528
|
-
/**
|
|
529
|
-
function
|
|
535
|
+
/** A text's size in UTF-8 bytes, as a step is measured where it's checked. */
|
|
536
|
+
function utf8Bytes(text) { try { return unescape(encodeURIComponent(text)).length; } catch (e) { return text.length * 3; } }
|
|
537
|
+
/** At most `max` bytes of a result go back to the model, as it's sent (UTF-8, its cut wrapper included), cut with a note. */
|
|
538
|
+
function actValue(value, max) {
|
|
530
539
|
var json = jsonState(value === undefined ? null : value), text;
|
|
531
540
|
try { text = JSON.stringify(json); } catch (e) { return { note: 'The result couldn\'t be read.' }; }
|
|
532
|
-
if (text
|
|
533
|
-
|
|
541
|
+
if (!text || utf8Bytes(text) <= max) return json;
|
|
542
|
+
// The cut is JSON inside JSON, so its quotes are escaped again: shrink it until the whole wrapper fits.
|
|
543
|
+
var cut = text.slice(0, max);
|
|
544
|
+
for (var i = 0; i < 24; i++) {
|
|
545
|
+
var wrapped = { cut: cut, note: 'Cut short: ask for less.' };
|
|
546
|
+
var size = utf8Bytes(JSON.stringify(wrapped));
|
|
547
|
+
if (size <= max) return wrapped;
|
|
548
|
+
cut = cut.slice(0, Math.max(0, Math.floor(cut.length * max / size) - 16));
|
|
549
|
+
}
|
|
550
|
+
return { note: 'Cut short: ask for less.' };
|
|
534
551
|
}
|
|
535
552
|
/** The page's own check of a call before it runs: a tool it declared, its arguments an object with nothing
|
|
536
553
|
* undeclared and nothing required missing. Lamp has checked the shapes; this is what the page itself can hold to. */
|
|
@@ -552,6 +569,15 @@
|
|
|
552
569
|
limits[k] = Math.max(1, Math.min(asked, ACT_LIMITS[k]));
|
|
553
570
|
});
|
|
554
571
|
var asked = o.limits && typeof o.limits.seconds === 'number' && o.limits.seconds > 0 ? o.limits.seconds : null;
|
|
572
|
+
var resultMax = o.limits && typeof o.limits.result === 'number' && o.limits.result > 0
|
|
573
|
+
? Math.min(Math.max(1024, Math.floor(o.limits.result)), ACT_RESULT_MAX) : ACT_RESULT;
|
|
574
|
+
// Each result takes from the turn's allowance, so later reads get less rather than the step growing past it.
|
|
575
|
+
var resultsLeft = ACT_TURN_RESULTS;
|
|
576
|
+
function sized(value) {
|
|
577
|
+
var kept = actValue(value, Math.max(1024, Math.min(resultMax, resultsLeft)));
|
|
578
|
+
try { resultsLeft = Math.max(0, resultsLeft - utf8Bytes(JSON.stringify(kept))); } catch (e) {}
|
|
579
|
+
return kept;
|
|
580
|
+
}
|
|
555
581
|
var id = o.turn || actId();
|
|
556
582
|
if (!actRan[id]) {
|
|
557
583
|
var kept = Object.keys(actRan);
|
|
@@ -601,7 +627,7 @@
|
|
|
601
627
|
}).then(function (value) {
|
|
602
628
|
if (value === NOT_RUN) return;
|
|
603
629
|
out.reads.push({ name: c.name, args: c.args });
|
|
604
|
-
results[c.id] = { id: c.id, ok: true, value:
|
|
630
|
+
results[c.id] = { id: c.id, ok: true, value: sized(value) };
|
|
605
631
|
}, function (e) { results[c.id] = { id: c.id, ok: false, error: refused(e) }; });
|
|
606
632
|
})).then(function () {
|
|
607
633
|
var stopped = null;
|
|
@@ -638,7 +664,7 @@
|
|
|
638
664
|
var running;
|
|
639
665
|
try { running = Promise.resolve(tool.run(c.args, call)); } catch (e) { running = Promise.reject(e); }
|
|
640
666
|
return running.then(function (value) {
|
|
641
|
-
var kept =
|
|
667
|
+
var kept = sized(value);
|
|
642
668
|
ran[key] = kept;
|
|
643
669
|
var entry = { name: c.name, args: c.args, result: value };
|
|
644
670
|
if (value && typeof value === 'object' && value.partly) entry.partly = String(value.partly);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"
|
|
1
|
+
{"exe":"ZqxAppName","name":"Zqx App Name","platform":"mac","instance":"10DC6CFC-3873-414E-B724-6AA904DC37A0"}
|
|
@@ -12017,6 +12017,9 @@
|
|
|
12017
12017
|
let request: String
|
|
12018
12018
|
var answers: [(question: String, answer: String)] = []
|
|
12019
12019
|
var writes = 0
|
|
12020
|
+
/// What its reads may still give back in all: every later step carries them again, under what a step may
|
|
12021
|
+
/// carry (`MAX_BODY` in lamp's app-ai.mjs).
|
|
12022
|
+
var resultsLeft = 1_200_000
|
|
12020
12023
|
}
|
|
12021
12024
|
private var operation: Operation?
|
|
12022
12025
|
/// Each change's result by its key, so the same change asked again gets it without running again.
|
|
@@ -12028,14 +12031,19 @@
|
|
|
12028
12031
|
private let send: ([String: Any], TimeInterval) async throws -> [String: Any]
|
|
12029
12032
|
/// The longest a request may take, when the app wants a quick answer; nil for no limit.
|
|
12030
12033
|
let seconds: Double?
|
|
12034
|
+
/// How much of one read goes back to the AI, in bytes: room for an app's real state in one step, since paging
|
|
12035
|
+
/// through it costs steps a request needs for its work.
|
|
12036
|
+
let resultBytes: Int
|
|
12031
12037
|
|
|
12032
12038
|
/// `seconds` limits each request's time, for a feature that should answer quickly; leave it out for no limit.
|
|
12039
|
+
/// `resultBytes` raises how much of a read the AI sees (32 KB unless given, 256 KB at most).
|
|
12033
12040
|
/// `send` stands in for Lamp in a test; leave it out.
|
|
12034
|
-
init(about: String = "", tools: [LampActTool], seconds: Double? = nil,
|
|
12041
|
+
init(about: String = "", tools: [LampActTool], seconds: Double? = nil, resultBytes: Int? = nil,
|
|
12035
12042
|
send: (([String: Any], TimeInterval) async throws -> [String: Any])? = nil) {
|
|
12036
12043
|
self.about = about
|
|
12037
12044
|
self.tools = tools
|
|
12038
12045
|
self.seconds = seconds.map { max(1, $0) }
|
|
12046
|
+
self.resultBytes = min(max(resultBytes ?? 32_768, 1024), 262_144)
|
|
12039
12047
|
self.send = send ?? { body, timeout in try await LampAI.post("/ai/act", body, timeout: timeout) }
|
|
12040
12048
|
}
|
|
12041
12049
|
|
|
@@ -12192,7 +12200,9 @@
|
|
|
12192
12200
|
onStep(.reading(tool.name))
|
|
12193
12201
|
do {
|
|
12194
12202
|
let value = try await tool.perform(args(call), callInfo(call, key: ""))
|
|
12195
|
-
|
|
12203
|
+
let kept = Self.cut(value, max: max(1024, min(resultBytes, op.resultsLeft)))
|
|
12204
|
+
op.resultsLeft = max(0, op.resultsLeft - ((try? JSONSerialization.data(withJSONObject: [kept]))?.count ?? 0))
|
|
12205
|
+
results[id] = ["id": id, "ok": true, "value": kept]
|
|
12196
12206
|
} catch {
|
|
12197
12207
|
results[id] = ["id": id, "ok": false, "error": Self.words(error)]
|
|
12198
12208
|
}
|
|
@@ -12251,13 +12261,21 @@
|
|
|
12251
12261
|
|
|
12252
12262
|
private static func words(_ error: Error) -> String { (error as? LampAI.Failure)?.message ?? error.localizedDescription }
|
|
12253
12263
|
|
|
12254
|
-
/// At most
|
|
12255
|
-
private static func cut(_ value: Any) -> Any {
|
|
12264
|
+
/// At most `max` bytes of a read go back to the AI, cut with a note.
|
|
12265
|
+
private static func cut(_ value: Any, max: Int) -> Any {
|
|
12256
12266
|
guard JSONSerialization.isValidJSONObject([value]), let data = try? JSONSerialization.data(withJSONObject: [value]) else {
|
|
12257
12267
|
return ["note": "The result couldn't be read."]
|
|
12258
12268
|
}
|
|
12259
|
-
guard data.count >
|
|
12260
|
-
|
|
12269
|
+
guard data.count > max else { return value }
|
|
12270
|
+
// The cut is JSON inside JSON, so its quotes are escaped again: shrink it until the whole wrapper fits.
|
|
12271
|
+
var cut = String(decoding: data.prefix(max), as: UTF8.self)
|
|
12272
|
+
for _ in 0..<24 {
|
|
12273
|
+
let wrapped: [String: Any] = ["cut": cut, "note": "Cut short: ask for less."]
|
|
12274
|
+
let size = (try? JSONSerialization.data(withJSONObject: [wrapped]))?.count ?? Int.max
|
|
12275
|
+
if size <= max { return wrapped }
|
|
12276
|
+
cut = String(cut.prefix(Swift.max(0, cut.count * max / Swift.max(size, 1) - 16)))
|
|
12277
|
+
}
|
|
12278
|
+
return ["note": "Cut short: ask for less."]
|
|
12261
12279
|
}
|
|
12262
12280
|
|
|
12263
12281
|
private static func canonical(_ value: Any) -> String {
|
|
@@ -521,16 +521,33 @@
|
|
|
521
521
|
// given ACT_STEP_SECONDS, which only a call that has stopped answering reaches. An app that wants a quicker turn
|
|
522
522
|
// asks for one (`limits: { seconds }`); steps and writes it may only lower.
|
|
523
523
|
var ACT_LIMITS = { steps: 4, writes: 6 };
|
|
524
|
+
// How much of one read goes back to the model: room for a real app's state (a 184-piece model, a week's calendar)
|
|
525
|
+
// in one step, since paging through it costs steps a turn needs for its work. An app with more raises it
|
|
526
|
+
// (`limits: { result }`), up to ACT_RESULT_MAX.
|
|
527
|
+
var ACT_RESULT = 32768, ACT_RESULT_MAX = 262144;
|
|
528
|
+
// What a turn's reads give back in all, which every later step carries again: kept under what a step may carry
|
|
529
|
+
// (`MAX_BODY` in lamp's app-ai.mjs), with room for the request, the tools and the steps' own words.
|
|
530
|
+
var ACT_TURN_RESULTS = 1200000;
|
|
524
531
|
var ACT_STEP_SECONDS = 600;
|
|
525
532
|
var actRan = {}; // turn id -> call key -> the result a write gave, while this copy of the page is open
|
|
526
533
|
function actId() { return 't' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8); }
|
|
527
534
|
function isList(v) { return Object.prototype.toString.call(v) === '[object Array]'; }
|
|
528
|
-
/**
|
|
529
|
-
function
|
|
535
|
+
/** A text's size in UTF-8 bytes, as a step is measured where it's checked. */
|
|
536
|
+
function utf8Bytes(text) { try { return unescape(encodeURIComponent(text)).length; } catch (e) { return text.length * 3; } }
|
|
537
|
+
/** At most `max` bytes of a result go back to the model, as it's sent (UTF-8, its cut wrapper included), cut with a note. */
|
|
538
|
+
function actValue(value, max) {
|
|
530
539
|
var json = jsonState(value === undefined ? null : value), text;
|
|
531
540
|
try { text = JSON.stringify(json); } catch (e) { return { note: 'The result couldn\'t be read.' }; }
|
|
532
|
-
if (text
|
|
533
|
-
|
|
541
|
+
if (!text || utf8Bytes(text) <= max) return json;
|
|
542
|
+
// The cut is JSON inside JSON, so its quotes are escaped again: shrink it until the whole wrapper fits.
|
|
543
|
+
var cut = text.slice(0, max);
|
|
544
|
+
for (var i = 0; i < 24; i++) {
|
|
545
|
+
var wrapped = { cut: cut, note: 'Cut short: ask for less.' };
|
|
546
|
+
var size = utf8Bytes(JSON.stringify(wrapped));
|
|
547
|
+
if (size <= max) return wrapped;
|
|
548
|
+
cut = cut.slice(0, Math.max(0, Math.floor(cut.length * max / size) - 16));
|
|
549
|
+
}
|
|
550
|
+
return { note: 'Cut short: ask for less.' };
|
|
534
551
|
}
|
|
535
552
|
/** The page's own check of a call before it runs: a tool it declared, its arguments an object with nothing
|
|
536
553
|
* undeclared and nothing required missing. Lamp has checked the shapes; this is what the page itself can hold to. */
|
|
@@ -552,6 +569,15 @@
|
|
|
552
569
|
limits[k] = Math.max(1, Math.min(asked, ACT_LIMITS[k]));
|
|
553
570
|
});
|
|
554
571
|
var asked = o.limits && typeof o.limits.seconds === 'number' && o.limits.seconds > 0 ? o.limits.seconds : null;
|
|
572
|
+
var resultMax = o.limits && typeof o.limits.result === 'number' && o.limits.result > 0
|
|
573
|
+
? Math.min(Math.max(1024, Math.floor(o.limits.result)), ACT_RESULT_MAX) : ACT_RESULT;
|
|
574
|
+
// Each result takes from the turn's allowance, so later reads get less rather than the step growing past it.
|
|
575
|
+
var resultsLeft = ACT_TURN_RESULTS;
|
|
576
|
+
function sized(value) {
|
|
577
|
+
var kept = actValue(value, Math.max(1024, Math.min(resultMax, resultsLeft)));
|
|
578
|
+
try { resultsLeft = Math.max(0, resultsLeft - utf8Bytes(JSON.stringify(kept))); } catch (e) {}
|
|
579
|
+
return kept;
|
|
580
|
+
}
|
|
555
581
|
var id = o.turn || actId();
|
|
556
582
|
if (!actRan[id]) {
|
|
557
583
|
var kept = Object.keys(actRan);
|
|
@@ -601,7 +627,7 @@
|
|
|
601
627
|
}).then(function (value) {
|
|
602
628
|
if (value === NOT_RUN) return;
|
|
603
629
|
out.reads.push({ name: c.name, args: c.args });
|
|
604
|
-
results[c.id] = { id: c.id, ok: true, value:
|
|
630
|
+
results[c.id] = { id: c.id, ok: true, value: sized(value) };
|
|
605
631
|
}, function (e) { results[c.id] = { id: c.id, ok: false, error: refused(e) }; });
|
|
606
632
|
})).then(function () {
|
|
607
633
|
var stopped = null;
|
|
@@ -638,7 +664,7 @@
|
|
|
638
664
|
var running;
|
|
639
665
|
try { running = Promise.resolve(tool.run(c.args, call)); } catch (e) { running = Promise.reject(e); }
|
|
640
666
|
return running.then(function (value) {
|
|
641
|
-
var kept =
|
|
667
|
+
var kept = sized(value);
|
|
642
668
|
ran[key] = kept;
|
|
643
669
|
var entry = { name: c.name, args: c.args, result: value };
|
|
644
670
|
if (value && typeof value === 'object' && value.partly) entry.partly = String(value.partly);
|
package/cli/local-ai.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import { answer as answerAI, engine } from "./ai.mjs";
|
|
|
21
21
|
/** The port lamp.js on a shared page asks (`LOCAL_AI` there): one, fixed, since the page can't be told another. */
|
|
22
22
|
export const LOCAL_AI_PORT = 47423;
|
|
23
23
|
const PLAY_HOST = "play.lamp.dev";
|
|
24
|
-
const MOST =
|
|
24
|
+
const MOST = 4_000_000, AT_ONCE = 3, ASKING_FOR = 120_000;
|
|
25
25
|
const ROUTES = new Set(["/ai/status", "/ai", "/ai/act", "/ai/decide", "/ai/hear", "/ai/image"]);
|
|
26
26
|
|
|
27
27
|
const keptFile = () => join(process.env.LAMP_DEV_HOME || join(homedir(), ".lamp", "dev"), "shared-pages-ai.json");
|