@north-light/sdk 0.0.0 → 0.3.414
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/LICENSE +18 -0
- package/README.md +187 -2
- package/dist/client.d.ts +162 -0
- package/dist/client.js +543 -0
- package/dist/error-codes.d.ts +7 -0
- package/dist/error-codes.js +41 -0
- package/dist/errors.d.ts +57 -0
- package/dist/errors.js +87 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +17 -0
- package/dist/keygen-cli.d.ts +2 -0
- package/dist/keygen-cli.js +6 -0
- package/dist/keygen-command.d.ts +6 -0
- package/dist/keygen-command.js +157 -0
- package/dist/oauth/index.d.ts +217 -0
- package/dist/oauth/index.js +535 -0
- package/dist/oauth/keygen.d.ts +18 -0
- package/dist/oauth/keygen.js +46 -0
- package/dist/oauth/refusal.d.ts +32 -0
- package/dist/oauth/refusal.js +17 -0
- package/dist/resources/activity.d.ts +22 -0
- package/dist/resources/activity.js +43 -0
- package/dist/resources/attachments.d.ts +15 -0
- package/dist/resources/attachments.js +13 -0
- package/dist/resources/bash.d.ts +9 -0
- package/dist/resources/bash.js +15 -0
- package/dist/resources/canvas/history.d.ts +11 -0
- package/dist/resources/canvas/history.js +20 -0
- package/dist/resources/canvas.d.ts +19 -0
- package/dist/resources/canvas.js +42 -0
- package/dist/resources/crons.d.ts +15 -0
- package/dist/resources/crons.js +33 -0
- package/dist/resources/custom-objects.d.ts +20 -0
- package/dist/resources/custom-objects.js +84 -0
- package/dist/resources/files.d.ts +34 -0
- package/dist/resources/files.js +34 -0
- package/dist/resources/follower.d.ts +71 -0
- package/dist/resources/follower.js +202 -0
- package/dist/resources/forward.d.ts +7 -0
- package/dist/resources/forward.js +67 -0
- package/dist/resources/grant-watcher.d.ts +57 -0
- package/dist/resources/grant-watcher.js +100 -0
- package/dist/resources/human/inbox.d.ts +14 -0
- package/dist/resources/human/inbox.js +30 -0
- package/dist/resources/human/requests.d.ts +13 -0
- package/dist/resources/human/requests.js +26 -0
- package/dist/resources/human.d.ts +8 -0
- package/dist/resources/human.js +10 -0
- package/dist/resources/identifiers.d.ts +8 -0
- package/dist/resources/identifiers.js +33 -0
- package/dist/resources/memory.d.ts +69 -0
- package/dist/resources/memory.js +30 -0
- package/dist/resources/models/config.d.ts +8 -0
- package/dist/resources/models/config.js +9 -0
- package/dist/resources/models/credentials.d.ts +10 -0
- package/dist/resources/models/credentials.js +17 -0
- package/dist/resources/models.d.ts +8 -0
- package/dist/resources/models.js +10 -0
- package/dist/resources/node-stream.d.ts +34 -0
- package/dist/resources/node-stream.js +176 -0
- package/dist/resources/nodes/jobs.d.ts +9 -0
- package/dist/resources/nodes/jobs.js +14 -0
- package/dist/resources/nodes/result.d.ts +8 -0
- package/dist/resources/nodes/result.js +11 -0
- package/dist/resources/nodes/worktree.d.ts +9 -0
- package/dist/resources/nodes/worktree.js +14 -0
- package/dist/resources/nodes.d.ts +23 -0
- package/dist/resources/nodes.js +47 -0
- package/dist/resources/providers.d.ts +51 -0
- package/dist/resources/providers.js +15 -0
- package/dist/resources/questions.d.ts +49 -0
- package/dist/resources/questions.js +21 -0
- package/dist/resources/request.d.ts +7 -0
- package/dist/resources/request.js +19 -0
- package/dist/resources/run-notice.d.ts +34 -0
- package/dist/resources/run-notice.js +67 -0
- package/dist/resources/run-reply.d.ts +79 -0
- package/dist/resources/run-reply.js +93 -0
- package/dist/resources/run-stream.d.ts +65 -0
- package/dist/resources/run-stream.js +243 -0
- package/dist/resources/run-watcher.d.ts +98 -0
- package/dist/resources/run-watcher.js +162 -0
- package/dist/resources/runs.d.ts +192 -0
- package/dist/resources/runs.js +208 -0
- package/dist/resources/shares.d.ts +40 -0
- package/dist/resources/shares.js +19 -0
- package/dist/resources/structured-requests.d.ts +14 -0
- package/dist/resources/structured-requests.js +19 -0
- package/dist/resources/uploads.d.ts +27 -0
- package/dist/resources/uploads.js +11 -0
- package/dist/schema.d.ts +7 -0
- package/dist/schema.js +10 -0
- package/dist/stores/postgres.d.ts +32 -0
- package/dist/stores/postgres.js +92 -0
- package/dist/testing/cloud.d.ts +209 -0
- package/dist/testing/cloud.js +880 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.js +3 -0
- package/dist/testing/notices.d.ts +70 -0
- package/dist/testing/notices.js +205 -0
- package/dist/testing/run.d.ts +172 -0
- package/dist/testing/run.js +429 -0
- package/dist/testing/users.d.ts +121 -0
- package/dist/testing/users.js +70 -0
- package/dist/testing/wire.d.ts +62 -0
- package/dist/testing/wire.js +148 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.js +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/package.json +56 -5
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { FakeCloud } from './cloud.js';
|
|
2
|
+
export type { FakeAnswer, FakeCall, FakeCloudOptions, FakeDirectoryCall, FakeDirectoryOp, FakeInterceptOp, FakeOp, FakeProvider, FakeRuns, FakeToolHandler, Intercept, } from './cloud.js';
|
|
3
|
+
export type { FakeRun, FakeRunEnding, FakeRunMessage, FakeRunParams, FakeQuestion } from './run.js';
|
|
4
|
+
export type { FakeNotice, RunNoticeOptions } from './notices.js';
|
|
5
|
+
export type { FakeMemoryDoc, FakeRefusalCode, FakeScope, FakeUser, GrantPatch, Responder, ResponderInput } from './users.js';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type JWK } from 'jose';
|
|
2
|
+
import type { FakeRun } from './run.js';
|
|
3
|
+
/** One notice attempt (`cloud.notices`). */
|
|
4
|
+
export interface FakeNotice {
|
|
5
|
+
runId: string;
|
|
6
|
+
userId: string;
|
|
7
|
+
/** 1–3. */
|
|
8
|
+
attempt: number;
|
|
9
|
+
/** Whether this request followed a 401 within the same attempt. */
|
|
10
|
+
remint: boolean;
|
|
11
|
+
latestSequence: number;
|
|
12
|
+
/** The decoded token payload. */
|
|
13
|
+
claims: Record<string, unknown>;
|
|
14
|
+
/** The app's status, or null when `deliver` threw. */
|
|
15
|
+
status: number | null;
|
|
16
|
+
/** The thrown message, or null. */
|
|
17
|
+
error: string | null;
|
|
18
|
+
at: number;
|
|
19
|
+
}
|
|
20
|
+
export interface RunNoticeOptions {
|
|
21
|
+
/** The app's registered `run_notice_url`: the token's `aud` and the POST target. */
|
|
22
|
+
url: string;
|
|
23
|
+
/** The app's route handler. */
|
|
24
|
+
deliver: (request: Request) => Promise<Response>;
|
|
25
|
+
/** Waits before attempts 2 and 3 (daemon: `[3_000, 5_000]`). Default `[0, 0]`. */
|
|
26
|
+
retryWaitsMs?: number[];
|
|
27
|
+
/** Gap after a delivered notice (daemon: 5_000). Default 0. */
|
|
28
|
+
ackGapMs?: number;
|
|
29
|
+
/** Gap after a dropped notice (daemon: 60_000). Default 0. */
|
|
30
|
+
failGapMs?: number;
|
|
31
|
+
}
|
|
32
|
+
/** The cloud's one ES256 key: unique `kid` per instance. */
|
|
33
|
+
export declare class SigningKey {
|
|
34
|
+
readonly kid: string;
|
|
35
|
+
private key;
|
|
36
|
+
private load;
|
|
37
|
+
jwks(): Promise<{
|
|
38
|
+
keys: JWK[];
|
|
39
|
+
}>;
|
|
40
|
+
sign(claims: Record<string, unknown>): Promise<string>;
|
|
41
|
+
}
|
|
42
|
+
/** The outbox: owed, coalesced, retried and re-minted as the daemon's. */
|
|
43
|
+
export declare class RunNotices {
|
|
44
|
+
private readonly options;
|
|
45
|
+
private readonly key;
|
|
46
|
+
private readonly identity;
|
|
47
|
+
/** Whether the run's person holds an active grant: a revoked grant has no notice target (`runNoticeTarget`). */
|
|
48
|
+
private readonly grantActive;
|
|
49
|
+
private readonly states;
|
|
50
|
+
readonly records: FakeNotice[];
|
|
51
|
+
constructor(options: RunNoticeOptions | undefined, key: SigningKey, identity: {
|
|
52
|
+
issuer: string;
|
|
53
|
+
clientId: string;
|
|
54
|
+
runtimeId: string;
|
|
55
|
+
runtimeClientId: string;
|
|
56
|
+
},
|
|
57
|
+
/** Whether the run's person holds an active grant: a revoked grant has no notice target (`runNoticeTarget`). */
|
|
58
|
+
grantActive: (userId: string) => boolean);
|
|
59
|
+
private state;
|
|
60
|
+
/** An open stream satisfies anything owed; closing it with the log ahead of what it wrote owes a notice. */
|
|
61
|
+
streamOpened(run: FakeRun): (lastWritten: number) => void;
|
|
62
|
+
appended(run: FakeRun): void;
|
|
63
|
+
forget(run: FakeRun): void;
|
|
64
|
+
reset(): void;
|
|
65
|
+
private owe;
|
|
66
|
+
private schedule;
|
|
67
|
+
private deliver;
|
|
68
|
+
private attempt;
|
|
69
|
+
private mint;
|
|
70
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Runtime→app run notices, mirroring `RunNoticeOutbox` (src/daemon/run-notices.ts) rule for rule, and the
|
|
2
|
+
// cloud's ES256 signing key that mints their tokens and is served as the directory JWKS.
|
|
3
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
4
|
+
import { SignJWT, decodeJwt, exportJWK, generateKeyPair } from 'jose';
|
|
5
|
+
/** The daemon's per-attempt bound (`RUN_NOTICE_ATTEMPT_TIMEOUT_MS`, src/daemon/run-notices.ts). */
|
|
6
|
+
const ATTEMPT_TIMEOUT_MS = 10_000;
|
|
7
|
+
let keySerial = 0;
|
|
8
|
+
/** The cloud's one ES256 key: unique `kid` per instance. */
|
|
9
|
+
export class SigningKey {
|
|
10
|
+
kid = `fake-${++keySerial}-${randomBytes(4).toString('hex')}`;
|
|
11
|
+
key;
|
|
12
|
+
load() {
|
|
13
|
+
this.key ??= (async () => {
|
|
14
|
+
const { privateKey, publicKey } = await generateKeyPair('ES256', { extractable: true });
|
|
15
|
+
const jwk = await exportJWK(publicKey);
|
|
16
|
+
return { privateKey: privateKey, jwk: { ...jwk, kid: this.kid, alg: 'ES256', use: 'sig' } };
|
|
17
|
+
})();
|
|
18
|
+
return this.key;
|
|
19
|
+
}
|
|
20
|
+
async jwks() { return { keys: [(await this.load()).jwk] }; }
|
|
21
|
+
async sign(claims) {
|
|
22
|
+
const { privateKey } = await this.load();
|
|
23
|
+
return new SignJWT(claims).setProtectedHeader({ alg: 'ES256', typ: 'at+jwt', kid: this.kid }).sign(privateKey);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
class Fault extends Error {
|
|
27
|
+
retryable;
|
|
28
|
+
constructor(retryable, message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.retryable = retryable;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** The outbox: owed, coalesced, retried and re-minted as the daemon's. */
|
|
34
|
+
export class RunNotices {
|
|
35
|
+
options;
|
|
36
|
+
key;
|
|
37
|
+
identity;
|
|
38
|
+
grantActive;
|
|
39
|
+
states = new Map();
|
|
40
|
+
records = [];
|
|
41
|
+
constructor(options, key, identity,
|
|
42
|
+
/** Whether the run's person holds an active grant: a revoked grant has no notice target (`runNoticeTarget`). */
|
|
43
|
+
grantActive) {
|
|
44
|
+
this.options = options;
|
|
45
|
+
this.key = key;
|
|
46
|
+
this.identity = identity;
|
|
47
|
+
this.grantActive = grantActive;
|
|
48
|
+
}
|
|
49
|
+
state(run) {
|
|
50
|
+
let state = this.states.get(run);
|
|
51
|
+
if (!state) {
|
|
52
|
+
state = { streams: 0, owed: false, inFlight: false, nextAllowedAt: 0 };
|
|
53
|
+
this.states.set(run, state);
|
|
54
|
+
}
|
|
55
|
+
return state;
|
|
56
|
+
}
|
|
57
|
+
/** An open stream satisfies anything owed; closing it with the log ahead of what it wrote owes a notice. */
|
|
58
|
+
streamOpened(run) {
|
|
59
|
+
const state = this.state(run);
|
|
60
|
+
state.streams++;
|
|
61
|
+
state.owed = false;
|
|
62
|
+
state.nextAllowedAt = 0;
|
|
63
|
+
if (state.timer) {
|
|
64
|
+
clearTimeout(state.timer);
|
|
65
|
+
state.timer = undefined;
|
|
66
|
+
}
|
|
67
|
+
let released = false;
|
|
68
|
+
return (lastWritten) => {
|
|
69
|
+
if (released)
|
|
70
|
+
return;
|
|
71
|
+
released = true;
|
|
72
|
+
state.streams--;
|
|
73
|
+
if (state.streams === 0 && run.latestSequence > lastWritten && !run.deleted)
|
|
74
|
+
this.owe(run);
|
|
75
|
+
else if (state.streams === 0 && !state.owed && !state.inFlight)
|
|
76
|
+
this.forget(run);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
appended(run) {
|
|
80
|
+
if (!this.options)
|
|
81
|
+
return;
|
|
82
|
+
if (this.state(run).streams > 0)
|
|
83
|
+
return;
|
|
84
|
+
this.owe(run);
|
|
85
|
+
}
|
|
86
|
+
forget(run) {
|
|
87
|
+
const state = this.states.get(run);
|
|
88
|
+
if (state?.timer)
|
|
89
|
+
clearTimeout(state.timer);
|
|
90
|
+
this.states.delete(run);
|
|
91
|
+
}
|
|
92
|
+
reset() {
|
|
93
|
+
for (const state of this.states.values())
|
|
94
|
+
if (state.timer)
|
|
95
|
+
clearTimeout(state.timer);
|
|
96
|
+
this.states.clear();
|
|
97
|
+
this.records.length = 0;
|
|
98
|
+
}
|
|
99
|
+
owe(run) {
|
|
100
|
+
if (!this.options || !this.grantActive(run.userId))
|
|
101
|
+
return;
|
|
102
|
+
const state = this.state(run);
|
|
103
|
+
state.owed = true;
|
|
104
|
+
this.schedule(run, state);
|
|
105
|
+
}
|
|
106
|
+
schedule(run, state) {
|
|
107
|
+
if (!state.owed || state.inFlight || state.timer || state.streams > 0)
|
|
108
|
+
return;
|
|
109
|
+
state.timer = setTimeout(() => { state.timer = undefined; void this.deliver(run, state); }, Math.max(0, state.nextAllowedAt - Date.now()));
|
|
110
|
+
state.timer.unref?.();
|
|
111
|
+
}
|
|
112
|
+
async deliver(run, state) {
|
|
113
|
+
const options = this.options;
|
|
114
|
+
if (!state.owed || state.inFlight || state.streams > 0 || run.deleted)
|
|
115
|
+
return;
|
|
116
|
+
if (!this.grantActive(run.userId)) {
|
|
117
|
+
state.owed = false;
|
|
118
|
+
this.forget(run);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
state.owed = false;
|
|
122
|
+
state.inFlight = true;
|
|
123
|
+
const latest = run.latestSequence;
|
|
124
|
+
const waits = options.retryWaitsMs ?? [0, 0];
|
|
125
|
+
let delivered = false;
|
|
126
|
+
let satisfied = false;
|
|
127
|
+
for (let attempt = 1;; attempt++) {
|
|
128
|
+
try {
|
|
129
|
+
await this.attempt(run, attempt, latest);
|
|
130
|
+
delivered = true;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (!(error instanceof Fault) || !error.retryable)
|
|
135
|
+
break;
|
|
136
|
+
const wait = waits[attempt - 1];
|
|
137
|
+
if (wait === undefined)
|
|
138
|
+
break;
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
140
|
+
if (state.streams > 0) {
|
|
141
|
+
satisfied = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
state.inFlight = false;
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
if (!delivered && !satisfied)
|
|
149
|
+
state.nextAllowedAt = now + (options.failGapMs ?? 0);
|
|
150
|
+
else if (delivered && state.streams === 0)
|
|
151
|
+
state.nextAllowedAt = now + (options.ackGapMs ?? 0);
|
|
152
|
+
if (state.owed)
|
|
153
|
+
this.schedule(run, state);
|
|
154
|
+
else if (state.streams === 0 && this.states.get(run) === state && !state.timer && now >= state.nextAllowedAt)
|
|
155
|
+
this.states.delete(run);
|
|
156
|
+
}
|
|
157
|
+
async attempt(run, attempt, latest) {
|
|
158
|
+
const options = this.options;
|
|
159
|
+
for (let remint = false;; remint = true) {
|
|
160
|
+
const token = await this.mint(run);
|
|
161
|
+
const record = { runId: run.id, userId: run.userId, attempt, remint, latestSequence: latest, claims: decodeJwt(token), status: null, error: null, at: Date.now() };
|
|
162
|
+
this.records.push(record);
|
|
163
|
+
let response;
|
|
164
|
+
// Each request is bounded as the daemon's (`RUN_NOTICE_ATTEMPT_TIMEOUT_MS`): a hung handler is a retryable failure.
|
|
165
|
+
const signal = AbortSignal.timeout(ATTEMPT_TIMEOUT_MS);
|
|
166
|
+
let timer;
|
|
167
|
+
try {
|
|
168
|
+
response = await Promise.race([
|
|
169
|
+
options.deliver(new Request(options.url, {
|
|
170
|
+
method: 'POST', headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
|
|
171
|
+
body: JSON.stringify({ run_id: run.id, latest_sequence: latest }), signal,
|
|
172
|
+
})),
|
|
173
|
+
new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(`no answer within ${ATTEMPT_TIMEOUT_MS}ms`)), ATTEMPT_TIMEOUT_MS); timer.unref?.(); }),
|
|
174
|
+
]);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
178
|
+
throw new Fault(true, record.error);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
}
|
|
183
|
+
record.status = response.status;
|
|
184
|
+
if (response.status >= 200 && response.status < 300)
|
|
185
|
+
return;
|
|
186
|
+
if (response.status === 401) {
|
|
187
|
+
if (!remint)
|
|
188
|
+
continue;
|
|
189
|
+
throw new Fault(true, 'unauthorized after re-mint');
|
|
190
|
+
}
|
|
191
|
+
if (response.status >= 500)
|
|
192
|
+
throw new Fault(true, `status ${response.status}`);
|
|
193
|
+
throw new Fault(false, `status ${response.status}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
mint(run) {
|
|
197
|
+
const now = Math.floor(Date.now() / 1000);
|
|
198
|
+
const app = `app:${this.identity.clientId}`;
|
|
199
|
+
return this.key.sign({
|
|
200
|
+
iss: this.identity.issuer, aud: this.options.url, sub: run.userId, grantee: app, client_id: this.identity.runtimeClientId,
|
|
201
|
+
scope: 'crtr:run_notice', initiating_app: app, act: { sub: this.identity.runtimeId, act: { sub: app } },
|
|
202
|
+
iat: now, exp: now + 300, jti: randomUUID(),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { DeclinedResultDTO, RunActivityDTO, RunEvent, RunEventFields, RunEventType, RunOutcomeDTO, RunQuestionSlotDTO, RunStatusDTO, TurnErrorDTO } from '@north-light/api';
|
|
2
|
+
/** What a FakeRun needs from the cloud that owns it. */
|
|
3
|
+
export interface RunHost {
|
|
4
|
+
readonly grantee: string;
|
|
5
|
+
readonly streamIdleMs: number;
|
|
6
|
+
/** Whether the run owner's grant is active now. */
|
|
7
|
+
grantActive(userId: string): boolean;
|
|
8
|
+
/** An event was appended (run-notice outbox). */
|
|
9
|
+
appended(run: FakeRun): void;
|
|
10
|
+
/** A stream opened; the returned function is called with the last sequence it wrote when it closes. */
|
|
11
|
+
streamOpened(run: FakeRun): (lastWritten: number) => void;
|
|
12
|
+
/** The run was deleted: drop its idempotency keys. */
|
|
13
|
+
deleted(run: FakeRun): void;
|
|
14
|
+
}
|
|
15
|
+
/** One message the run accepted (`run.start` `message` or `run.message`). */
|
|
16
|
+
export interface FakeRunMessage {
|
|
17
|
+
message_id: string;
|
|
18
|
+
text: string;
|
|
19
|
+
start_turn: boolean;
|
|
20
|
+
sequence_number: number;
|
|
21
|
+
idempotencyKey: string | null;
|
|
22
|
+
delivered: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The `run.start` body as the fake received it. */
|
|
25
|
+
export interface FakeRunParams {
|
|
26
|
+
prompt: string;
|
|
27
|
+
message?: string;
|
|
28
|
+
scopes?: string[];
|
|
29
|
+
isolated?: boolean;
|
|
30
|
+
/** An isolated run's working folder, when the app named one. */
|
|
31
|
+
cwd?: string;
|
|
32
|
+
output_schema?: Record<string, unknown>;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
profile?: string;
|
|
35
|
+
}
|
|
36
|
+
/** How `settle` ends a run. */
|
|
37
|
+
export type FakeRunEnding = {
|
|
38
|
+
result: unknown;
|
|
39
|
+
} | {
|
|
40
|
+
declined: DeclinedResultDTO;
|
|
41
|
+
} | {
|
|
42
|
+
failure: 'canceled' | 'deadline_exceeded';
|
|
43
|
+
} | {
|
|
44
|
+
runtimeError: string;
|
|
45
|
+
};
|
|
46
|
+
/** A question `ask` opens; defaults to one text slot `answer`. */
|
|
47
|
+
export interface FakeQuestion {
|
|
48
|
+
title: string;
|
|
49
|
+
subtitle?: string;
|
|
50
|
+
steps?: number;
|
|
51
|
+
slots?: RunQuestionSlotDTO[];
|
|
52
|
+
provider?: string;
|
|
53
|
+
tool?: string;
|
|
54
|
+
}
|
|
55
|
+
interface OpenQuestion {
|
|
56
|
+
question_id: string;
|
|
57
|
+
title: string;
|
|
58
|
+
subtitle?: string;
|
|
59
|
+
steps: number;
|
|
60
|
+
slots: RunQuestionSlotDTO[];
|
|
61
|
+
status: 'open' | 'answered' | 'canceled';
|
|
62
|
+
answer?: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
/** `activity.tool.summary` as the daemon writes it: a command's first line, else a path; null otherwise. */
|
|
65
|
+
export declare function toolCallSummary(args: unknown): string | null;
|
|
66
|
+
/** One run the app started, and the test's handle on it. */
|
|
67
|
+
export declare class FakeRun {
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly userId: string;
|
|
70
|
+
private readonly host;
|
|
71
|
+
private readonly metadata;
|
|
72
|
+
readonly scopes: string[] | null;
|
|
73
|
+
/** The run's start-time body, as received. */
|
|
74
|
+
readonly params: FakeRunParams;
|
|
75
|
+
/** The `Idempotency-Key` of the start, or null. */
|
|
76
|
+
readonly idempotencyKey: string | null;
|
|
77
|
+
/** Every message the run accepted; the start's `message` is the first, with `start_turn: false`. */
|
|
78
|
+
readonly messages: FakeRunMessage[];
|
|
79
|
+
name: string;
|
|
80
|
+
readonly createdAt: string;
|
|
81
|
+
lastActivityAt: string;
|
|
82
|
+
/** Kept events (`expireEvents` drops the oldest). */
|
|
83
|
+
private log;
|
|
84
|
+
private sequence;
|
|
85
|
+
private outcomeValue;
|
|
86
|
+
private resultValue;
|
|
87
|
+
private hasResult;
|
|
88
|
+
private settledAt;
|
|
89
|
+
private turnError;
|
|
90
|
+
private rested;
|
|
91
|
+
private answered;
|
|
92
|
+
private override;
|
|
93
|
+
private messageSerial;
|
|
94
|
+
private outcomeRevision;
|
|
95
|
+
private finalReport;
|
|
96
|
+
readonly questions: Map<string, OpenQuestion>;
|
|
97
|
+
private readonly streams;
|
|
98
|
+
private readonly waiters;
|
|
99
|
+
deleted: boolean;
|
|
100
|
+
constructor(id: string, userId: string, params: FakeRunParams, idempotencyKey: string | null, host: RunHost, metadata: Record<string, unknown>, scopes: string[] | null);
|
|
101
|
+
/** Every kept event, in order. */
|
|
102
|
+
get events(): readonly RunEvent[];
|
|
103
|
+
get latestSequence(): number;
|
|
104
|
+
get earliestSequence(): number | null;
|
|
105
|
+
get outcome(): RunOutcomeDTO | undefined;
|
|
106
|
+
get result(): unknown;
|
|
107
|
+
get lastTurnError(): TurnErrorDTO | undefined;
|
|
108
|
+
/** Messages not yet delivered to a turn (the start's `message` included). */
|
|
109
|
+
get pending(): FakeRunMessage[];
|
|
110
|
+
get pendingQuestionIds(): string[];
|
|
111
|
+
/** The run status, derived as the daemon's `runStatus` derives it. */
|
|
112
|
+
get status(): RunStatusDTO;
|
|
113
|
+
/** What the root is doing, derived from the event log as `runActivity` derives it with no open streaming block. */
|
|
114
|
+
get activity(): RunActivityDTO | null;
|
|
115
|
+
/** `run.get`'s body. */
|
|
116
|
+
toObject(): Record<string, unknown>;
|
|
117
|
+
/** Append any run event; `nodeId` defaults to the root (another id is a child node's event). */
|
|
118
|
+
emit<T extends RunEventType>(type: T, fields: RunEventFields<T>, { nodeId }?: {
|
|
119
|
+
nodeId?: string;
|
|
120
|
+
}): RunEvent;
|
|
121
|
+
/** One turn answering `text`: turn started, `chunks` deltas, text done, turn completed. Delivers `messageIds`
|
|
122
|
+
* (default: every pending message). */
|
|
123
|
+
reply(text: string, { chunks, messageIds }?: {
|
|
124
|
+
chunks?: number;
|
|
125
|
+
messageIds?: string[];
|
|
126
|
+
}): void;
|
|
127
|
+
/** The same turn ending in error: `last_turn_error` set, status idle. */
|
|
128
|
+
failTurn({ code, message, messageIds }: {
|
|
129
|
+
code: TurnErrorDTO['code'];
|
|
130
|
+
message: string;
|
|
131
|
+
messageIds?: string[];
|
|
132
|
+
}): void;
|
|
133
|
+
/** Fake-only: status `running` with `activity` overridden until the next appended event. */
|
|
134
|
+
setActivity(activity: RunActivityDTO, { at }?: {
|
|
135
|
+
at?: string | Date | number;
|
|
136
|
+
}): void;
|
|
137
|
+
/** The run goes idle without a new turn (it stopped without submitting); undelivered start-turn mail is consumed. */
|
|
138
|
+
idle(): void;
|
|
139
|
+
/** Record the run's ending and emit `node.settled`. */
|
|
140
|
+
settle(ending: FakeRunEnding): void;
|
|
141
|
+
/** Open a question: a `waiting_on_user` event; the run waits on the user between turns. Returns its id. */
|
|
142
|
+
ask(question: FakeQuestion): string;
|
|
143
|
+
/** Delete the run as the runtime does: reads answer 404, streams close, its idempotency keys are dropped. */
|
|
144
|
+
delete(): void;
|
|
145
|
+
/** Idle-close every open stream now. */
|
|
146
|
+
closeIdle(): void;
|
|
147
|
+
/** Drop kept events up to `upTo` (event expiry; makes `stream_gap` reachable). */
|
|
148
|
+
expireEvents(upTo: number): void;
|
|
149
|
+
/** Record an accepted message; returns it. */
|
|
150
|
+
accept(text: string, startTurn: boolean, idempotencyKey: string | null): FakeRunMessage;
|
|
151
|
+
/** Clear a non-canceled ending so a start-turn message runs it again (`commitReopenResident`). */
|
|
152
|
+
reopen(): void;
|
|
153
|
+
/** The app answered a question. */
|
|
154
|
+
answer(questionId: string, responses: Record<string, unknown>): void;
|
|
155
|
+
questionObject(questionId: string): Record<string, unknown> | undefined;
|
|
156
|
+
/** Resolves when the run rests (`idle`, `waiting_on_user`, `settled`), is deleted, `seconds` pass, or `signal` aborts. */
|
|
157
|
+
waitResting(seconds: number, signal?: AbortSignal | null): Promise<void>;
|
|
158
|
+
/** The SSE body after `after`, as `streamRunEvents` writes it. Validation happens before the caller calls this. */
|
|
159
|
+
openStream(after: number, signal?: AbortSignal | null): ReadableStream<Uint8Array>;
|
|
160
|
+
/** The grant went inactive: every open stream gets the 401 `grant_revoked` error frame. */
|
|
161
|
+
revokeStreams(): void;
|
|
162
|
+
get openStreams(): number;
|
|
163
|
+
/** Notify held reads that state changed. */
|
|
164
|
+
wake(): void;
|
|
165
|
+
private assertLive;
|
|
166
|
+
private turnOpen;
|
|
167
|
+
private everStartedTurn;
|
|
168
|
+
private turnsStarted;
|
|
169
|
+
private turn;
|
|
170
|
+
private append;
|
|
171
|
+
}
|
|
172
|
+
export {};
|