broapp 0.4.2 → 0.4.3
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/package.json +4 -4
- package/src/ai/host/create-ai.ts +27 -2
- package/src/ai/host/index.ts +1 -0
- package/src/ai/host/run.ts +10 -2
- package/src/host/runtime.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "broapp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Build tooling and runtime for local applications made of a Bun host, a browser UI, and a Brobridge connection between them",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"./package.json": "./package.json"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@brobridgejs/client": "^0.2.
|
|
53
|
-
"@brobridgejs/core": "^0.2.
|
|
54
|
-
"brobridge": "^0.2.
|
|
52
|
+
"@brobridgejs/client": "^0.2.2",
|
|
53
|
+
"@brobridgejs/core": "^0.2.2",
|
|
54
|
+
"brobridge": "^0.2.2"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"react": ">=18",
|
package/src/ai/host/create-ai.ts
CHANGED
|
@@ -71,6 +71,18 @@ export interface InProcessTurn {
|
|
|
71
71
|
readonly history?: readonly ChatTurn[];
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/** One question put to an in-process turn's stand-in. */
|
|
75
|
+
export interface InProcessQuestion {
|
|
76
|
+
readonly tool: string;
|
|
77
|
+
readonly input: unknown;
|
|
78
|
+
/** The approval table's key, `<runId>:<callId>`. */
|
|
79
|
+
readonly requestId: string;
|
|
80
|
+
/** The call the question is about, as `ai.chatConfirm` names it with the run id. */
|
|
81
|
+
readonly callId: string;
|
|
82
|
+
/** When the question stops waiting, when the gate said. */
|
|
83
|
+
readonly expiresAt?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
74
86
|
/** How {@link Ai.turn} is answered and stopped. */
|
|
75
87
|
export interface InProcessTurnOptions {
|
|
76
88
|
/**
|
|
@@ -78,8 +90,14 @@ export interface InProcessTurnOptions {
|
|
|
78
90
|
*
|
|
79
91
|
* The caller is the person's stand-in, so it decides exactly as the person
|
|
80
92
|
* would have: the gate still asks, and still records the answer.
|
|
93
|
+
*
|
|
94
|
+
* `'defer'` answers nothing. The question stays in the approval table for
|
|
95
|
+
* somebody else to answer by its request id, over `ai.chatConfirm` as a
|
|
96
|
+
* person in a chat would, and the gate's own window still ends it. That is
|
|
97
|
+
* for a stand-in that answers some questions itself and brings the rest to a
|
|
98
|
+
* person: whoever answers, each question is answered once.
|
|
81
99
|
*/
|
|
82
|
-
readonly answer: (question:
|
|
100
|
+
readonly answer: (question: InProcessQuestion) => boolean | 'defer';
|
|
83
101
|
/** Aborting it cancels the turn, as a browser's cancel would. */
|
|
84
102
|
readonly signal?: AbortSignal;
|
|
85
103
|
readonly onEvent?: (event: ChatEvent) => void;
|
|
@@ -398,7 +416,14 @@ export function createAi(options: CreateAiOptions): Ai {
|
|
|
398
416
|
// The gate's request id is `<runId>:<callId>`, which is what an
|
|
399
417
|
// event without one would have named.
|
|
400
418
|
const requestId = event.requestId ?? `${turn.runId}:${event.callId}`;
|
|
401
|
-
|
|
419
|
+
const answer = turnOptions.answer({
|
|
420
|
+
tool: event.tool ?? '',
|
|
421
|
+
input: event.input,
|
|
422
|
+
requestId,
|
|
423
|
+
callId: event.callId ?? '',
|
|
424
|
+
...(event.expiresAt === undefined ? {} : { expiresAt: event.expiresAt }),
|
|
425
|
+
});
|
|
426
|
+
if (answer !== 'defer') void settle(requestId, answer);
|
|
402
427
|
}
|
|
403
428
|
return Promise.resolve();
|
|
404
429
|
},
|
package/src/ai/host/index.ts
CHANGED
package/src/ai/host/run.ts
CHANGED
|
@@ -288,14 +288,22 @@ function boundOutput(output: unknown, max: number): unknown {
|
|
|
288
288
|
return { type: 'text', value: cut(json, max) };
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
/**
|
|
291
|
+
/**
|
|
292
|
+
* One transcript message with every tool input and output bounded.
|
|
293
|
+
*
|
|
294
|
+
* A cut input stays an object, `{ truncated: "<head><omitted N chars>" }`,
|
|
295
|
+
* rather than becoming the string itself: an OpenAI-compatible provider sends
|
|
296
|
+
* a call's input as its `arguments`, and Ollama refuses a whole request whose
|
|
297
|
+
* arguments are not a JSON object ("invalid tool call arguments"). The first
|
|
298
|
+
* 12j evaluation lost a turn to exactly that.
|
|
299
|
+
*/
|
|
292
300
|
function boundMessage(message: ResponseMessage, limits: HistoryLimits): ModelMessage {
|
|
293
301
|
if (!Array.isArray(message.content)) return message;
|
|
294
302
|
const content = (message.content as readonly unknown[]).map((part) => {
|
|
295
303
|
if (!isRecord(part)) return part;
|
|
296
304
|
if (part['type'] === 'tool-call') {
|
|
297
305
|
const json = JSON.stringify(part['input']) ?? '';
|
|
298
|
-
return json.length <= limits.inputChars ? part : { ...part, input: cut(json, limits.inputChars) };
|
|
306
|
+
return json.length <= limits.inputChars ? part : { ...part, input: { truncated: cut(json, limits.inputChars) } };
|
|
299
307
|
}
|
|
300
308
|
if (part['type'] === 'tool-result') return { ...part, output: boundOutput(part['output'], limits.outputChars) };
|
|
301
309
|
return part;
|
package/src/host/runtime.ts
CHANGED
|
@@ -76,6 +76,15 @@ export interface RunningApp {
|
|
|
76
76
|
* looking attached for that whole minute.
|
|
77
77
|
*/
|
|
78
78
|
readonly attached: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* A fresh address for this application, carrying a new one-time launch token.
|
|
81
|
+
*
|
|
82
|
+
* Brobridge mints the token; each address works once and expires on the
|
|
83
|
+
* bridge's launch-token lifetime. Call it on the host's own decision — a
|
|
84
|
+
* person's click in an authenticated tab, a local process that already holds
|
|
85
|
+
* a credential — never because a browser asked. Do not log the result.
|
|
86
|
+
*/
|
|
87
|
+
launchUrl(): string;
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
const POLL_INTERVAL_MS = 1_000;
|
|
@@ -215,5 +224,6 @@ export async function startApp(options: StartAppOptions): Promise<RunningApp> {
|
|
|
215
224
|
get attached() {
|
|
216
225
|
return isAttached();
|
|
217
226
|
},
|
|
227
|
+
launchUrl: () => bridge.launchUrl(),
|
|
218
228
|
};
|
|
219
229
|
}
|