@bubblebrain-ai/bubble 0.0.54 → 0.0.56
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/README.md +43 -0
- package/dist/agent/input-controller.d.ts +3 -0
- package/dist/agent/input-controller.js +13 -0
- package/dist/agent.js +7 -1
- package/dist/context/budget.js +7 -2
- package/dist/model-catalog.d.ts +1 -1
- package/dist/model-catalog.js +12 -2
- package/dist/model-pricing.d.ts +1 -5
- package/dist/model-pricing.js +0 -0
- package/dist/provider-ai-sdk.js +5 -0
- package/dist/provider-ark-responses.d.ts +12 -2
- package/dist/provider-ark-responses.js +68 -13
- package/dist/provider-registry.js +34 -10
- package/dist/provider.js +7 -1
- package/dist/sdk/index.d.ts +97 -6
- package/dist/sdk/index.js +445 -34
- package/dist/sdk/replay-event-log.d.ts +19 -0
- package/dist/sdk/replay-event-log.js +72 -0
- package/dist/sdk/session-turn-coordinator.d.ts +45 -0
- package/dist/sdk/session-turn-coordinator.js +227 -0
- package/dist/tui/app.js +5 -3
- package/dist/tui/controller/controller.js +12 -6
- package/dist/tui/fullscreen.js +2 -1
- package/dist/tui/model/image-paste.js +9 -10
- package/dist/types.d.ts +14 -2
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small append-only event log for SDK streams. Producers never wait for a
|
|
3
|
+
* consumer, and consumers can attach again with their last seen sequence.
|
|
4
|
+
*/
|
|
5
|
+
export class ReplayEventLog {
|
|
6
|
+
entries = [];
|
|
7
|
+
signal = deferredSignal();
|
|
8
|
+
closed = false;
|
|
9
|
+
failure;
|
|
10
|
+
get length() {
|
|
11
|
+
return this.entries.length;
|
|
12
|
+
}
|
|
13
|
+
append(value) {
|
|
14
|
+
if (this.closed)
|
|
15
|
+
return;
|
|
16
|
+
this.entries.push(value);
|
|
17
|
+
this.wake();
|
|
18
|
+
}
|
|
19
|
+
close(error) {
|
|
20
|
+
if (this.closed)
|
|
21
|
+
return;
|
|
22
|
+
this.closed = true;
|
|
23
|
+
this.failure = error;
|
|
24
|
+
this.wake();
|
|
25
|
+
}
|
|
26
|
+
iterate(options = {}) {
|
|
27
|
+
const start = Math.max(0, options.from ?? 0);
|
|
28
|
+
return this.readFrom(start, options.signal);
|
|
29
|
+
}
|
|
30
|
+
async *readFrom(start, abortSignal) {
|
|
31
|
+
let index = start;
|
|
32
|
+
while (true) {
|
|
33
|
+
while (index < this.entries.length)
|
|
34
|
+
yield this.entries[index++];
|
|
35
|
+
if (this.closed) {
|
|
36
|
+
if (this.failure !== undefined)
|
|
37
|
+
throw this.failure;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (abortSignal?.aborted)
|
|
41
|
+
return;
|
|
42
|
+
const currentSignal = this.signal.promise;
|
|
43
|
+
await waitForSignal(currentSignal, abortSignal);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
wake() {
|
|
47
|
+
const previous = this.signal;
|
|
48
|
+
this.signal = deferredSignal();
|
|
49
|
+
previous.resolve();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function deferredSignal() {
|
|
53
|
+
let resolve;
|
|
54
|
+
const promise = new Promise((done) => {
|
|
55
|
+
resolve = done;
|
|
56
|
+
});
|
|
57
|
+
return { promise, resolve };
|
|
58
|
+
}
|
|
59
|
+
function waitForSignal(signal, abortSignal) {
|
|
60
|
+
if (!abortSignal)
|
|
61
|
+
return signal;
|
|
62
|
+
if (abortSignal.aborted)
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
const done = () => {
|
|
66
|
+
abortSignal.removeEventListener("abort", done);
|
|
67
|
+
resolve();
|
|
68
|
+
};
|
|
69
|
+
abortSignal.addEventListener("abort", done, { once: true });
|
|
70
|
+
signal.then(done);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type SessionTurnPhase = "queued" | "reserved" | "starting" | "active" | "stopping" | "settled";
|
|
2
|
+
export interface SessionTurnQueueState {
|
|
3
|
+
active: boolean;
|
|
4
|
+
queued: number;
|
|
5
|
+
phase: Exclude<SessionTurnPhase, "queued" | "settled"> | "idle" | "deleted";
|
|
6
|
+
}
|
|
7
|
+
export interface SessionTurnReservation {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly sessionId: string;
|
|
10
|
+
readonly signal: AbortSignal;
|
|
11
|
+
readonly phase: SessionTurnPhase;
|
|
12
|
+
readonly completion: Promise<void>;
|
|
13
|
+
waitForStart(): Promise<void>;
|
|
14
|
+
markActive(): void;
|
|
15
|
+
cancel(error?: Error): void;
|
|
16
|
+
finish(): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* One synchronous state machine owns the complete per-session turn lifecycle.
|
|
20
|
+
* A reservation and its AbortController are created when runTurn() is called,
|
|
21
|
+
* before its async iterator is consumed, so stop/delete never have a handoff
|
|
22
|
+
* window in which a turn is invisible.
|
|
23
|
+
*/
|
|
24
|
+
export declare class SessionTurnCoordinator {
|
|
25
|
+
private readonly sessions;
|
|
26
|
+
private readonly deletedSessions;
|
|
27
|
+
private readonly turnIdPrefix;
|
|
28
|
+
private nextTurnId;
|
|
29
|
+
reserve(sessionId: string, signal?: AbortSignal): SessionTurnReservation;
|
|
30
|
+
getCurrent(sessionId: string): SessionTurnReservation | undefined;
|
|
31
|
+
getState(sessionId: string): SessionTurnQueueState;
|
|
32
|
+
stop(sessionId: string, message?: string): number;
|
|
33
|
+
stopCurrent(sessionId: string, message?: string): number;
|
|
34
|
+
clearQueue(sessionId: string, message?: string): number;
|
|
35
|
+
delete(sessionId: string): Promise<void>;
|
|
36
|
+
isDeleted(sessionId: string): boolean;
|
|
37
|
+
revive(sessionId: string): void;
|
|
38
|
+
private reservationFor;
|
|
39
|
+
private cancelTurn;
|
|
40
|
+
private finishTurn;
|
|
41
|
+
private settleDetached;
|
|
42
|
+
private promoteNext;
|
|
43
|
+
private deleteEmptySession;
|
|
44
|
+
private detachExternalAbort;
|
|
45
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { AgentAbortError, throwIfAborted } from "../agent/abort-errors.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
/**
|
|
4
|
+
* One synchronous state machine owns the complete per-session turn lifecycle.
|
|
5
|
+
* A reservation and its AbortController are created when runTurn() is called,
|
|
6
|
+
* before its async iterator is consumed, so stop/delete never have a handoff
|
|
7
|
+
* window in which a turn is invisible.
|
|
8
|
+
*/
|
|
9
|
+
export class SessionTurnCoordinator {
|
|
10
|
+
sessions = new Map();
|
|
11
|
+
deletedSessions = new Set();
|
|
12
|
+
turnIdPrefix = `sdk-turn-${randomUUID().slice(0, 6)}`;
|
|
13
|
+
nextTurnId = 0;
|
|
14
|
+
reserve(sessionId, signal) {
|
|
15
|
+
if (this.deletedSessions.has(sessionId)) {
|
|
16
|
+
throw new Error(`Session is deleted: ${sessionId}`);
|
|
17
|
+
}
|
|
18
|
+
throwIfAborted(signal);
|
|
19
|
+
const turn = {
|
|
20
|
+
id: `${this.turnIdPrefix}-${++this.nextTurnId}`,
|
|
21
|
+
sessionId,
|
|
22
|
+
phase: "queued",
|
|
23
|
+
abort: new AbortController(),
|
|
24
|
+
start: deferred(),
|
|
25
|
+
completion: deferred(),
|
|
26
|
+
externalSignal: signal,
|
|
27
|
+
};
|
|
28
|
+
// A reservation can legally be stopped before its iterator is consumed.
|
|
29
|
+
// Keep that rejected start promise observed until waitForStart() reads it.
|
|
30
|
+
void turn.start.promise.catch(() => undefined);
|
|
31
|
+
let session = this.sessions.get(sessionId);
|
|
32
|
+
if (!session) {
|
|
33
|
+
session = { queued: [] };
|
|
34
|
+
this.sessions.set(sessionId, session);
|
|
35
|
+
}
|
|
36
|
+
if (!session.current) {
|
|
37
|
+
session.current = turn;
|
|
38
|
+
turn.phase = "reserved";
|
|
39
|
+
turn.start.resolve(undefined);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
session.queued.push(turn);
|
|
43
|
+
}
|
|
44
|
+
if (signal) {
|
|
45
|
+
turn.onExternalAbort = () => {
|
|
46
|
+
this.cancelTurn(turn, abortReason(signal, "SDK turn cancelled."));
|
|
47
|
+
};
|
|
48
|
+
signal.addEventListener("abort", turn.onExternalAbort, { once: true });
|
|
49
|
+
}
|
|
50
|
+
return this.reservationFor(turn);
|
|
51
|
+
}
|
|
52
|
+
getCurrent(sessionId) {
|
|
53
|
+
const turn = this.sessions.get(sessionId)?.current;
|
|
54
|
+
return turn ? this.reservationFor(turn) : undefined;
|
|
55
|
+
}
|
|
56
|
+
getState(sessionId) {
|
|
57
|
+
if (this.deletedSessions.has(sessionId)) {
|
|
58
|
+
return { active: false, queued: 0, phase: "deleted" };
|
|
59
|
+
}
|
|
60
|
+
const session = this.sessions.get(sessionId);
|
|
61
|
+
const currentPhase = session?.current?.phase;
|
|
62
|
+
return {
|
|
63
|
+
active: Boolean(session?.current),
|
|
64
|
+
queued: session?.queued.length ?? 0,
|
|
65
|
+
phase: currentPhase && currentPhase !== "queued" && currentPhase !== "settled"
|
|
66
|
+
? currentPhase
|
|
67
|
+
: "idle",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
stop(sessionId, message = "SDK turn stopped.") {
|
|
71
|
+
const session = this.sessions.get(sessionId);
|
|
72
|
+
if (!session)
|
|
73
|
+
return 0;
|
|
74
|
+
const turns = [session.current, ...session.queued].filter((turn) => Boolean(turn));
|
|
75
|
+
for (const turn of turns)
|
|
76
|
+
this.cancelTurn(turn, new AgentAbortError(message));
|
|
77
|
+
return turns.length;
|
|
78
|
+
}
|
|
79
|
+
stopCurrent(sessionId, message = "SDK turn stopped.") {
|
|
80
|
+
const current = this.sessions.get(sessionId)?.current;
|
|
81
|
+
if (!current)
|
|
82
|
+
return 0;
|
|
83
|
+
this.cancelTurn(current, new AgentAbortError(message));
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
clearQueue(sessionId, message = "Queued SDK turn cancelled.") {
|
|
87
|
+
const queued = [...(this.sessions.get(sessionId)?.queued ?? [])];
|
|
88
|
+
for (const turn of queued)
|
|
89
|
+
this.cancelTurn(turn, new AgentAbortError(message));
|
|
90
|
+
return queued.length;
|
|
91
|
+
}
|
|
92
|
+
async delete(sessionId) {
|
|
93
|
+
this.deletedSessions.add(sessionId);
|
|
94
|
+
const session = this.sessions.get(sessionId);
|
|
95
|
+
const turns = session
|
|
96
|
+
? [session.current, ...session.queued].filter((turn) => Boolean(turn))
|
|
97
|
+
: [];
|
|
98
|
+
for (const turn of turns) {
|
|
99
|
+
this.cancelTurn(turn, new AgentAbortError("SDK session deleted."));
|
|
100
|
+
}
|
|
101
|
+
await Promise.all(turns.map((turn) => turn.completion.promise));
|
|
102
|
+
}
|
|
103
|
+
isDeleted(sessionId) {
|
|
104
|
+
return this.deletedSessions.has(sessionId);
|
|
105
|
+
}
|
|
106
|
+
revive(sessionId) {
|
|
107
|
+
const session = this.sessions.get(sessionId);
|
|
108
|
+
if (session?.current || session?.queued.length) {
|
|
109
|
+
throw new Error(`Cannot recreate session while turns are still settling: ${sessionId}`);
|
|
110
|
+
}
|
|
111
|
+
this.deletedSessions.delete(sessionId);
|
|
112
|
+
}
|
|
113
|
+
reservationFor(turn) {
|
|
114
|
+
return {
|
|
115
|
+
id: turn.id,
|
|
116
|
+
sessionId: turn.sessionId,
|
|
117
|
+
signal: turn.abort.signal,
|
|
118
|
+
get phase() {
|
|
119
|
+
return turn.phase;
|
|
120
|
+
},
|
|
121
|
+
completion: turn.completion.promise,
|
|
122
|
+
waitForStart: async () => {
|
|
123
|
+
await turn.start.promise;
|
|
124
|
+
if (turn.phase === "settled")
|
|
125
|
+
throw turn.terminalError ?? new AgentAbortError("SDK turn cancelled.");
|
|
126
|
+
if (turn.phase === "reserved")
|
|
127
|
+
turn.phase = "starting";
|
|
128
|
+
throwIfAborted(turn.abort.signal);
|
|
129
|
+
},
|
|
130
|
+
markActive: () => {
|
|
131
|
+
throwIfAborted(turn.abort.signal);
|
|
132
|
+
if (turn.phase === "starting")
|
|
133
|
+
turn.phase = "active";
|
|
134
|
+
},
|
|
135
|
+
cancel: (error = new AgentAbortError("SDK turn cancelled.")) => {
|
|
136
|
+
this.cancelTurn(turn, error);
|
|
137
|
+
},
|
|
138
|
+
finish: () => this.finishTurn(turn),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
cancelTurn(turn, error) {
|
|
142
|
+
if (turn.phase === "settled")
|
|
143
|
+
return;
|
|
144
|
+
turn.terminalError = error;
|
|
145
|
+
turn.abort.abort(error);
|
|
146
|
+
const session = this.sessions.get(turn.sessionId);
|
|
147
|
+
if (!session) {
|
|
148
|
+
this.settleDetached(turn);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (turn.phase === "queued") {
|
|
152
|
+
const index = session.queued.indexOf(turn);
|
|
153
|
+
if (index >= 0)
|
|
154
|
+
session.queued.splice(index, 1);
|
|
155
|
+
this.settleDetached(turn);
|
|
156
|
+
this.deleteEmptySession(turn.sessionId, session);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (turn.phase === "reserved") {
|
|
160
|
+
if (session.current === turn)
|
|
161
|
+
session.current = undefined;
|
|
162
|
+
this.settleDetached(turn);
|
|
163
|
+
this.promoteNext(turn.sessionId, session);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
turn.phase = "stopping";
|
|
167
|
+
}
|
|
168
|
+
finishTurn(turn) {
|
|
169
|
+
if (turn.phase === "settled")
|
|
170
|
+
return;
|
|
171
|
+
const session = this.sessions.get(turn.sessionId);
|
|
172
|
+
if (session?.current === turn)
|
|
173
|
+
session.current = undefined;
|
|
174
|
+
this.settleDetached(turn);
|
|
175
|
+
if (session)
|
|
176
|
+
this.promoteNext(turn.sessionId, session);
|
|
177
|
+
}
|
|
178
|
+
settleDetached(turn) {
|
|
179
|
+
this.detachExternalAbort(turn);
|
|
180
|
+
const error = turn.terminalError;
|
|
181
|
+
turn.phase = "settled";
|
|
182
|
+
if (error)
|
|
183
|
+
turn.start.reject(error);
|
|
184
|
+
turn.completion.resolve(undefined);
|
|
185
|
+
}
|
|
186
|
+
promoteNext(sessionId, session) {
|
|
187
|
+
if (this.deletedSessions.has(sessionId)) {
|
|
188
|
+
for (const queued of [...session.queued]) {
|
|
189
|
+
this.cancelTurn(queued, new AgentAbortError("SDK session deleted."));
|
|
190
|
+
}
|
|
191
|
+
this.deleteEmptySession(sessionId, session);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
while (!session.current && session.queued.length > 0) {
|
|
195
|
+
const next = session.queued.shift();
|
|
196
|
+
if (!next || next.phase === "settled")
|
|
197
|
+
continue;
|
|
198
|
+
session.current = next;
|
|
199
|
+
next.phase = "reserved";
|
|
200
|
+
next.start.resolve(undefined);
|
|
201
|
+
}
|
|
202
|
+
this.deleteEmptySession(sessionId, session);
|
|
203
|
+
}
|
|
204
|
+
deleteEmptySession(sessionId, session) {
|
|
205
|
+
if (!session.current && session.queued.length === 0)
|
|
206
|
+
this.sessions.delete(sessionId);
|
|
207
|
+
}
|
|
208
|
+
detachExternalAbort(turn) {
|
|
209
|
+
if (turn.externalSignal && turn.onExternalAbort) {
|
|
210
|
+
turn.externalSignal.removeEventListener("abort", turn.onExternalAbort);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function deferred() {
|
|
215
|
+
let resolve;
|
|
216
|
+
let reject;
|
|
217
|
+
const promise = new Promise((onResolve, onReject) => {
|
|
218
|
+
resolve = onResolve;
|
|
219
|
+
reject = onReject;
|
|
220
|
+
});
|
|
221
|
+
return { promise, resolve, reject };
|
|
222
|
+
}
|
|
223
|
+
function abortReason(signal, fallback) {
|
|
224
|
+
if (signal.reason instanceof Error)
|
|
225
|
+
return signal.reason;
|
|
226
|
+
return new AgentAbortError(typeof signal.reason === "string" ? signal.reason : fallback);
|
|
227
|
+
}
|
package/dist/tui/app.js
CHANGED
|
@@ -483,8 +483,8 @@ export class PiTuiApp {
|
|
|
483
483
|
// into a text steer.
|
|
484
484
|
if (payload.images.length > 0)
|
|
485
485
|
controller.queueInput(payload);
|
|
486
|
-
else
|
|
487
|
-
controller.
|
|
486
|
+
else if (!controller.steer(trimmed))
|
|
487
|
+
controller.queueInput(payload);
|
|
488
488
|
this.renderSnapshot();
|
|
489
489
|
}
|
|
490
490
|
else if (controller.queueAfterCommand?.(payload)) {
|
|
@@ -520,7 +520,9 @@ export class PiTuiApp {
|
|
|
520
520
|
this.pushUserRow({ text: command, images: [] });
|
|
521
521
|
const controller = this.options.controller;
|
|
522
522
|
if (controller.isRunning()) {
|
|
523
|
-
controller.steer(skillInvocation.actualPrompt)
|
|
523
|
+
if (!controller.steer(skillInvocation.actualPrompt)) {
|
|
524
|
+
controller.queueInput(skillInvocation.actualPrompt);
|
|
525
|
+
}
|
|
524
526
|
this.renderSnapshot();
|
|
525
527
|
}
|
|
526
528
|
else {
|
|
@@ -449,7 +449,9 @@ export class BubbleTuiController {
|
|
|
449
449
|
steer(content) {
|
|
450
450
|
if (!this.runActive || !this.activeInputController)
|
|
451
451
|
return false;
|
|
452
|
-
const input = this.activeInputController.
|
|
452
|
+
const input = this.activeInputController.tryEnqueue(content);
|
|
453
|
+
if (!input)
|
|
454
|
+
return false;
|
|
453
455
|
const displayKey = nextDisplayMessageKey("steer");
|
|
454
456
|
beginSteer(this.queue, {
|
|
455
457
|
id: input.id,
|
|
@@ -539,11 +541,15 @@ export class BubbleTuiController {
|
|
|
539
541
|
throw new Error("A command is already running.");
|
|
540
542
|
}
|
|
541
543
|
if (this.runActive) {
|
|
542
|
-
if (typeof input === "string")
|
|
543
|
-
this.steer(input)
|
|
544
|
+
if (typeof input === "string") {
|
|
545
|
+
if (!this.steer(input))
|
|
546
|
+
this.queueInput(input);
|
|
547
|
+
}
|
|
544
548
|
else if (isSubmitPayload(input)) {
|
|
545
|
-
if (input.images.length === 0)
|
|
546
|
-
this.steer(input.text)
|
|
549
|
+
if (input.images.length === 0) {
|
|
550
|
+
if (!this.steer(input.text))
|
|
551
|
+
this.queueInput(input);
|
|
552
|
+
}
|
|
547
553
|
else
|
|
548
554
|
this.queueInput(input);
|
|
549
555
|
}
|
|
@@ -716,7 +722,7 @@ export class BubbleTuiController {
|
|
|
716
722
|
this.notify();
|
|
717
723
|
}
|
|
718
724
|
finally {
|
|
719
|
-
const leftoverSteers = inputController.
|
|
725
|
+
const leftoverSteers = inputController.closePendingInputs();
|
|
720
726
|
// The signal is the source of truth. A provider may observe cancellation
|
|
721
727
|
// and end its iterator without throwing, but the user's pending steers
|
|
722
728
|
// must still be dropped exactly as they are in the Ink implementation.
|
package/dist/tui/fullscreen.js
CHANGED
|
@@ -118,7 +118,8 @@ export class FullscreenApp {
|
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
if (this.options.controller.isRunning()) {
|
|
121
|
-
this.options.controller.steer(trimmed)
|
|
121
|
+
if (!this.options.controller.steer(trimmed))
|
|
122
|
+
this.options.controller.queueInput(trimmed);
|
|
122
123
|
}
|
|
123
124
|
else if (this.options.controller.queueAfterCommand?.(trimmed)) {
|
|
124
125
|
this.render();
|
|
@@ -299,20 +299,19 @@ export async function getImageFromClipboard() {
|
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
async function getClipboardImageDarwin() {
|
|
302
|
-
// Probe first — `as «class PNGf»` throws if clipboard has no image.
|
|
303
|
-
try {
|
|
304
|
-
await execFileAsync("osascript", ["-e", "the clipboard as «class PNGf»"], {
|
|
305
|
-
timeout: 5000,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
302
|
const tmp = path.join(os.tmpdir(), `bubble_clip_${Date.now()}_${process.pid}.png`);
|
|
312
303
|
const script = `set png_data to (the clipboard as «class PNGf»)\n` +
|
|
313
304
|
`set fp to open for access POSIX file "${tmp}" with write permission\n` +
|
|
305
|
+
`try\n` +
|
|
306
|
+
`set eof fp to 0\n` +
|
|
314
307
|
`write png_data to fp\n` +
|
|
315
|
-
`close access fp
|
|
308
|
+
`close access fp\n` +
|
|
309
|
+
`on error err_msg number err_num\n` +
|
|
310
|
+
`try\n` +
|
|
311
|
+
`close access fp\n` +
|
|
312
|
+
`end try\n` +
|
|
313
|
+
`error err_msg number err_num\n` +
|
|
314
|
+
`end try`;
|
|
316
315
|
try {
|
|
317
316
|
await execFileAsync("osascript", ["-e", script], { timeout: 5000 });
|
|
318
317
|
const buf = await fs.readFile(tmp);
|
package/dist/types.d.ts
CHANGED
|
@@ -18,12 +18,14 @@ export type ReasoningEffort = ThinkingLevel;
|
|
|
18
18
|
export type ProviderRawContentBlock = Record<string, unknown> & {
|
|
19
19
|
type: string;
|
|
20
20
|
};
|
|
21
|
-
export type ProviderMetadataProvider = "anthropic" | "google";
|
|
21
|
+
export type ProviderMetadataProvider = "anthropic" | "google" | "openai";
|
|
22
22
|
export interface ProviderContentBlockStore {
|
|
23
23
|
contentBlocks?: ProviderRawContentBlock[];
|
|
24
24
|
}
|
|
25
25
|
export interface AssistantProviderMetadata {
|
|
26
26
|
anthropic?: ProviderContentBlockStore;
|
|
27
|
+
/** OpenAI Responses reasoning items captured for stateless tool-loop replay. */
|
|
28
|
+
openai?: ProviderContentBlockStore;
|
|
27
29
|
/**
|
|
28
30
|
* Gemini raw parts captured for replay: reasoning/tool-call parts carrying
|
|
29
31
|
* thoughtSignature so multi-turn thinking round-trips through our own
|
|
@@ -368,10 +370,14 @@ export interface AgentRunInput {
|
|
|
368
370
|
submittedAt?: number;
|
|
369
371
|
}
|
|
370
372
|
export interface AgentInputController {
|
|
373
|
+
/** Enqueue only while the current run can still accept boundary input. */
|
|
374
|
+
tryEnqueue?(content: string): AgentRunInput | undefined;
|
|
371
375
|
drainPendingInputs(): AgentRunInput[];
|
|
372
376
|
pendingInputCount(): number;
|
|
377
|
+
/** Atomically stop accepting input and return everything still pending. */
|
|
378
|
+
closePendingInputs?(): AgentRunInput[];
|
|
373
379
|
}
|
|
374
|
-
export type AgentInputRejectedReason = "no_continuation";
|
|
380
|
+
export type AgentInputRejectedReason = "no_continuation" | "turn_failed" | "turn_cancelled";
|
|
375
381
|
export type AgentEvent = {
|
|
376
382
|
type: "turn_start";
|
|
377
383
|
} | {
|
|
@@ -455,6 +461,12 @@ export type AgentEvent = {
|
|
|
455
461
|
id: string;
|
|
456
462
|
content: string;
|
|
457
463
|
target: "current_turn";
|
|
464
|
+
} | {
|
|
465
|
+
type: "input_queued";
|
|
466
|
+
id: string;
|
|
467
|
+
content: string;
|
|
468
|
+
turnId: string;
|
|
469
|
+
target: "next_turn";
|
|
458
470
|
} | {
|
|
459
471
|
type: "input_rejected";
|
|
460
472
|
id: string;
|