@auersperg/flare 0.3.10 → 0.3.12
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/cli/{chunk-m0698kap.js → chunk-1yvbecac.js} +88 -5
- package/dist/cli/{chunk-znktbexz.js → chunk-7ym0f672.js} +6 -6
- package/dist/cli/{chunk-jrptpjmm.js → chunk-8gzfscca.js} +5 -7
- package/dist/cli/chunk-xn3e3gev.js +9 -0
- package/dist/cli/{chunk-zt96ghm7.js → chunk-yhvbksrm.js} +20 -1
- package/dist/cli/flare.js +4 -4
- package/dist/platform/admin.js +5 -2
- package/dist/platform/dashboard/_astro/ai-view.De8j8CVD.js +1 -0
- package/dist/platform/dashboard/_astro/{auth-view.BhUFPmNv.js → auth-view.C9j0-OzH.js} +1 -1
- package/dist/platform/dashboard/_astro/client.BvQ8bQkQ.js +4 -0
- package/dist/platform/dashboard/_astro/{data-view.C-VU8Oxm.js → data-view.Bw2dG8Wg.js} +1 -1
- package/dist/platform/dashboard/_astro/{functions-view.3yLrtS0h.js → functions-view.Bsazyfej.js} +1 -1
- package/dist/platform/dashboard/_astro/index.CDrYoVkO.css +2 -0
- package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.C_U84Sz-.js +3 -0
- package/dist/platform/dashboard/_astro/input-group.Dsc4z0iT.js +1 -0
- package/dist/platform/dashboard/_astro/{sdk-view.BAiQKNm8.js → sdk-view.vdDnLZ5g.js} +1 -1
- package/dist/platform/dashboard/_astro/{services-view.BSrqpyr5.js → services-view.C1FjN8NY.js} +1 -1
- package/dist/platform/dashboard/_astro/{settings-view.C_eYjlIE.js → settings-view.DZldw4Vy.js} +1 -1
- package/dist/platform/dashboard/bootstrap/action.js +43 -0
- package/dist/platform/dashboard/bootstrap/openapi.json +1 -1
- package/dist/platform/dashboard/bootstrap/release.json +2 -2
- package/dist/platform/dashboard/index.html +1 -1
- package/dist/platform/manifest.json +43 -38
- package/dist/platform/runtime.js +24294 -836
- package/dist/sdk/client.d.ts +6 -0
- package/dist/sdk/client.js +29 -0
- package/dist/sdk/cloudflare.d.ts +4 -0
- package/dist/sdk/cloudflare.js +4 -0
- package/dist/sdk/codex-stream.d.ts +28 -0
- package/dist/sdk/codex-stream.js +147 -0
- package/dist/sdk/codex.d.ts +20 -0
- package/dist/sdk/codex.js +17 -0
- package/dist/sdk/protocol.d.ts +6 -0
- package/dist/sdk/protocol.js +12 -0
- package/dist/sdk/react.d.ts +4 -0
- package/dist/sdk/search.d.ts +25 -0
- package/dist/sdk/search.js +22 -0
- package/dist/sdk/server.d.ts +15 -1
- package/dist/sdk/server.js +9 -1
- package/package.json +1 -1
- package/dist/platform/dashboard/_astro/client.4z54xJ_z.js +0 -1
- package/dist/platform/dashboard/_astro/index.BPQdjJ4j.css +0 -2
- package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.Dsfj1oeZ.js +0 -3
- package/dist/platform/dashboard/_astro/input-group.BZ1TZT7L.js +0 -1
package/dist/sdk/client.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type CodexSubscription } from "./codex-stream.js";
|
|
2
|
+
export type { CodexEvent, CodexSubscription } from "./codex-stream.js";
|
|
1
3
|
import { FlareError, type FunctionRef } from "./protocol.js";
|
|
2
4
|
export { reference, FlareError } from "./protocol.js";
|
|
3
5
|
export type { FunctionRef, Json } from "./protocol.js";
|
|
@@ -41,6 +43,10 @@ export declare function createClient(options: ClientOptions): {
|
|
|
41
43
|
query: <I, O>(ref: FunctionRef<I, O>, args: I) => Promise<O>;
|
|
42
44
|
mutation: <I, O>(ref: FunctionRef<I, O>, args: I) => Promise<O>;
|
|
43
45
|
action: <I, O>(ref: FunctionRef<I, O>, args: I) => Promise<O>;
|
|
46
|
+
codexEvents<I>(ref: FunctionRef<I, CodexSubscription>, args: I, streamOptions?: {
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
after?: number;
|
|
49
|
+
}): AsyncGenerator<import("./codex-stream.js").CodexEvent, void, any>;
|
|
44
50
|
/** One subscription delivers the initial snapshot and subsequent updates. */
|
|
45
51
|
onUpdate<I, O>(ref: FunctionRef<I, O>, args: I, next: (value: O) => void, error?: (error: FlareError) => void): () => void;
|
|
46
52
|
/** Call on login/logout/account switch; never reuse cached values across identities. */
|
package/dist/sdk/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readCodexEvents } from "./codex-stream.js";
|
|
1
2
|
import { readBody } from "./http.js";
|
|
2
3
|
import {
|
|
3
4
|
assertPath,
|
|
@@ -279,6 +280,34 @@ export function createClient(options) {
|
|
|
279
280
|
query: invoke("query"),
|
|
280
281
|
mutation: invoke("mutation"),
|
|
281
282
|
action: invoke("action"),
|
|
283
|
+
async* codexEvents(ref, args, streamOptions = {}) {
|
|
284
|
+
ensureOpen();
|
|
285
|
+
assertPath(ref.path);
|
|
286
|
+
const controller = new AbortController;
|
|
287
|
+
const abort = () => controller.abort();
|
|
288
|
+
cleanups.add(abort);
|
|
289
|
+
streamOptions.signal?.addEventListener("abort", abort, { once: true });
|
|
290
|
+
if (streamOptions.signal?.aborted)
|
|
291
|
+
abort();
|
|
292
|
+
try {
|
|
293
|
+
yield* readCodexEvents({
|
|
294
|
+
url: endpoint("/v1/codex/events"),
|
|
295
|
+
ref,
|
|
296
|
+
args,
|
|
297
|
+
after: streamOptions.after,
|
|
298
|
+
transport,
|
|
299
|
+
signal: controller.signal,
|
|
300
|
+
token: async () => {
|
|
301
|
+
ensureOpen();
|
|
302
|
+
return await options.getToken?.() ?? null;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
} finally {
|
|
306
|
+
abort();
|
|
307
|
+
cleanups.delete(abort);
|
|
308
|
+
streamOptions.signal?.removeEventListener("abort", abort);
|
|
309
|
+
}
|
|
310
|
+
},
|
|
282
311
|
onUpdate(ref, args, next, error) {
|
|
283
312
|
const observer = { next, error };
|
|
284
313
|
ensureOpen();
|
package/dist/sdk/cloudflare.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { type FunctionTree } from "./server.js";
|
|
|
3
3
|
import { type Auth, type FunctionKind, type Json } from "./protocol.js";
|
|
4
4
|
import { type Schema } from "./schema.js";
|
|
5
5
|
export interface Invocation {
|
|
6
|
+
/** Trusted Runtime admission only, never copied from public call input. */
|
|
7
|
+
mcp?: string;
|
|
6
8
|
path: string;
|
|
7
9
|
kind: FunctionKind;
|
|
8
10
|
args: Json;
|
|
@@ -98,6 +100,8 @@ export interface ScopedCapability {
|
|
|
98
100
|
}): Promise<string>;
|
|
99
101
|
download(id: string): Promise<Response>;
|
|
100
102
|
fileCommand(op: string, input: unknown): Promise<any>;
|
|
103
|
+
codexCommand(op: string, input: unknown): Promise<any>;
|
|
104
|
+
searchCommand(op: string, input: unknown): Promise<any>;
|
|
101
105
|
scheduleCommand(op: string, input: unknown): Promise<any>;
|
|
102
106
|
}
|
|
103
107
|
/** Construct the class at build time. Parent supplies a verified identity and release ID,
|
package/dist/sdk/cloudflare.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { codexContext } from "./codex.js";
|
|
2
|
+
import { searchContext } from "./search.js";
|
|
1
3
|
import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
|
|
2
4
|
import {
|
|
3
5
|
flatten,
|
|
@@ -359,6 +361,8 @@ export function createAction(app) {
|
|
|
359
361
|
const capability = this.env.CAPABILITY;
|
|
360
362
|
const context = {
|
|
361
363
|
auth: call.auth,
|
|
364
|
+
codex: codexContext((op, input) => rpcData(capability.codexCommand(op, input))),
|
|
365
|
+
search: searchContext((op, input) => rpcData(capability.searchCommand(op, input))),
|
|
362
366
|
schedules: {
|
|
363
367
|
set: (input) => rpcData(capability.scheduleCommand("set", input)),
|
|
364
368
|
cancel: (name) => rpcData(capability.scheduleCommand("cancel", { name })),
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type FunctionRef } from "./protocol.js";
|
|
2
|
+
import type { CodexTask } from "./codex.js";
|
|
3
|
+
export type CodexSubscription = {
|
|
4
|
+
type: "codex-events";
|
|
5
|
+
id: string;
|
|
6
|
+
};
|
|
7
|
+
export type CodexEvent = {
|
|
8
|
+
type: "task";
|
|
9
|
+
task: CodexTask;
|
|
10
|
+
} | {
|
|
11
|
+
type: "status" | "text" | "output" | "item" | "truncated";
|
|
12
|
+
seq: number;
|
|
13
|
+
at: number;
|
|
14
|
+
itemId?: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
kind?: string;
|
|
17
|
+
state?: string;
|
|
18
|
+
exitCode?: number | null;
|
|
19
|
+
};
|
|
20
|
+
export declare function readCodexEvents<I>(options: {
|
|
21
|
+
url: string;
|
|
22
|
+
ref: FunctionRef<I, CodexSubscription>;
|
|
23
|
+
args: I;
|
|
24
|
+
token: () => Promise<string | null>;
|
|
25
|
+
transport: typeof fetch;
|
|
26
|
+
signal: AbortSignal;
|
|
27
|
+
after?: number;
|
|
28
|
+
}): AsyncGenerator<CodexEvent>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FlareError,
|
|
3
|
+
boundedJson,
|
|
4
|
+
LIMITS
|
|
5
|
+
} from "./protocol.js";
|
|
6
|
+
import { readBody } from "./http.js";
|
|
7
|
+
export async function* readCodexEvents(options) {
|
|
8
|
+
if (options.ref.kind !== "query")
|
|
9
|
+
throw new FlareError("INVALID_KIND", "Stream authorization must be a read-only query");
|
|
10
|
+
let cursor = options.after ?? 0, failures = 0;
|
|
11
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > 4096)
|
|
12
|
+
throw new FlareError("INVALID_ARGUMENT", "Invalid cursor");
|
|
13
|
+
const signal = options.signal;
|
|
14
|
+
while (!signal.aborted) {
|
|
15
|
+
let reader;
|
|
16
|
+
const connection = new AbortController;
|
|
17
|
+
const abort = () => connection.abort(signal.reason);
|
|
18
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
19
|
+
if (signal.aborted)
|
|
20
|
+
abort();
|
|
21
|
+
let idle;
|
|
22
|
+
const touch = () => {
|
|
23
|
+
clearTimeout(idle);
|
|
24
|
+
idle = setTimeout(() => connection.abort(), 45000);
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
touch();
|
|
28
|
+
const token = await options.token();
|
|
29
|
+
if (signal.aborted)
|
|
30
|
+
return;
|
|
31
|
+
const response = await options.transport(options.url, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
redirect: "error",
|
|
34
|
+
signal: connection.signal,
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
38
|
+
},
|
|
39
|
+
body: boundedJson({
|
|
40
|
+
path: options.ref.path,
|
|
41
|
+
kind: "query",
|
|
42
|
+
args: options.args,
|
|
43
|
+
after: cursor
|
|
44
|
+
}, LIMITS.inputBytes)
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const bytes = response.body ? await readBody(response.body, 8192, () => {
|
|
48
|
+
throw new FlareError("INVALID_RESPONSE", "Oversized error");
|
|
49
|
+
}) : new Uint8Array;
|
|
50
|
+
let code = `HTTP_${response.status}`;
|
|
51
|
+
try {
|
|
52
|
+
const value = JSON.parse(new TextDecoder().decode(bytes));
|
|
53
|
+
if (/^[A-Z][A-Z0-9_]{0,63}$/.test(value.error))
|
|
54
|
+
code = value.error;
|
|
55
|
+
} catch {}
|
|
56
|
+
throw new FlareError(code, "Event subscription failed");
|
|
57
|
+
}
|
|
58
|
+
if (!response.headers.get("content-type")?.startsWith("text/event-stream") || !response.body)
|
|
59
|
+
throw new FlareError("INVALID_RESPONSE", "Expected SSE");
|
|
60
|
+
reader = response.body.getReader();
|
|
61
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
62
|
+
let pending = "", total = 0;
|
|
63
|
+
while (!signal.aborted) {
|
|
64
|
+
const next = await reader.read();
|
|
65
|
+
if (next.done)
|
|
66
|
+
break;
|
|
67
|
+
touch();
|
|
68
|
+
total += next.value.byteLength;
|
|
69
|
+
if (total > 1048576)
|
|
70
|
+
throw new FlareError("INVALID_RESPONSE", "Stream exceeds bound");
|
|
71
|
+
pending += decoder.decode(next.value, { stream: true });
|
|
72
|
+
let end;
|
|
73
|
+
while ((end = pending.indexOf(`
|
|
74
|
+
|
|
75
|
+
`)) >= 0) {
|
|
76
|
+
const frame = pending.slice(0, end);
|
|
77
|
+
pending = pending.slice(end + 2);
|
|
78
|
+
if (frame.length > 524288)
|
|
79
|
+
throw new FlareError("INVALID_RESPONSE", "Oversized event");
|
|
80
|
+
let name = "", data = "", id = "";
|
|
81
|
+
for (const line of frame.split(`
|
|
82
|
+
`)) {
|
|
83
|
+
if (line.startsWith("event:"))
|
|
84
|
+
name = line.slice(6).trim();
|
|
85
|
+
if (line.startsWith("data:"))
|
|
86
|
+
data += line.slice(5).trimStart();
|
|
87
|
+
if (line.startsWith("id:"))
|
|
88
|
+
id = line.slice(3).trim();
|
|
89
|
+
}
|
|
90
|
+
if (!data)
|
|
91
|
+
continue;
|
|
92
|
+
if (name === "reconnect")
|
|
93
|
+
continue;
|
|
94
|
+
const value = JSON.parse(data);
|
|
95
|
+
if (name === "task") {
|
|
96
|
+
if (!value || typeof value.id !== "string" || !["succeeded", "failed", "cancelled", "interrupted"].includes(value.state))
|
|
97
|
+
throw new FlareError("INVALID_RESPONSE", "Invalid task");
|
|
98
|
+
yield { type: "task", task: value };
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (name !== "progress" || !Number.isSafeInteger(value.seq) || String(value.seq) !== id || value.seq > 4096 || !["status", "text", "output", "item", "truncated"].includes(value.type))
|
|
102
|
+
throw new FlareError("INVALID_RESPONSE", "Invalid event");
|
|
103
|
+
if (value.seq <= cursor)
|
|
104
|
+
continue;
|
|
105
|
+
if (value.seq !== cursor + 1)
|
|
106
|
+
throw new FlareError("CODEX_EVENT_GAP", "Event gap");
|
|
107
|
+
cursor = value.seq;
|
|
108
|
+
failures = 0;
|
|
109
|
+
yield value;
|
|
110
|
+
}
|
|
111
|
+
if (pending.length > 524288)
|
|
112
|
+
throw new FlareError("INVALID_RESPONSE", "Oversized frame");
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (signal.aborted)
|
|
116
|
+
return;
|
|
117
|
+
const code = error instanceof FlareError ? error.code : "";
|
|
118
|
+
if (code && ![
|
|
119
|
+
"HTTP_429",
|
|
120
|
+
"HTTP_500",
|
|
121
|
+
"HTTP_502",
|
|
122
|
+
"HTTP_503",
|
|
123
|
+
"HTTP_504",
|
|
124
|
+
"CODEX_UNAVAILABLE"
|
|
125
|
+
].includes(code))
|
|
126
|
+
throw error;
|
|
127
|
+
if (++failures > 5)
|
|
128
|
+
throw new FlareError("CODEX_STREAM_INTERRUPTED", "Reconnect failed; task continues independently");
|
|
129
|
+
} finally {
|
|
130
|
+
clearTimeout(idle);
|
|
131
|
+
signal.removeEventListener("abort", abort);
|
|
132
|
+
connection.abort();
|
|
133
|
+
await reader?.cancel().catch(() => {});
|
|
134
|
+
}
|
|
135
|
+
if (signal.aborted)
|
|
136
|
+
return;
|
|
137
|
+
await new Promise((resolve) => {
|
|
138
|
+
const done = () => {
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
signal.removeEventListener("abort", done);
|
|
141
|
+
resolve();
|
|
142
|
+
};
|
|
143
|
+
const timer = setTimeout(done, Math.min(5000, 500 * 2 ** failures));
|
|
144
|
+
signal.addEventListener("abort", done, { once: true });
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface CodexTaskInput {
|
|
2
|
+
/** Stable UUID for retries of the same submission; retained for seven days. */
|
|
3
|
+
id: string;
|
|
4
|
+
prompt: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CodexTask {
|
|
7
|
+
id: string;
|
|
8
|
+
state: "running" | "succeeded" | "failed" | "cancelled" | "interrupted";
|
|
9
|
+
startedAt: number;
|
|
10
|
+
finishedAt?: number;
|
|
11
|
+
result?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CodexContext {
|
|
15
|
+
start(input: CodexTaskInput): Promise<CodexTask>;
|
|
16
|
+
status(id: string): Promise<CodexTask | null>;
|
|
17
|
+
cancel(id: string): Promise<CodexTask | null>;
|
|
18
|
+
}
|
|
19
|
+
export declare function codexInput(op: string, input: any): void;
|
|
20
|
+
export declare function codexContext(call: (op: string, input: unknown) => Promise<any>): CodexContext;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FlareError, boundedJson } from "./protocol.js";
|
|
2
|
+
export function codexInput(op, input) {
|
|
3
|
+
boundedJson(input, 20000);
|
|
4
|
+
if (!input || Array.isArray(input) || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(input.id) || !["start", "status", "cancel"].includes(op) || Object.keys(input).some((k) => !["id", ...op === "start" ? ["prompt"] : []].includes(k)) || op === "start" && (typeof input.prompt !== "string" || !input.prompt.trim() || new TextEncoder().encode(input.prompt).length > 16384))
|
|
5
|
+
throw new FlareError("INVALID_ARGUMENT", "Invalid Codex task");
|
|
6
|
+
}
|
|
7
|
+
export function codexContext(call) {
|
|
8
|
+
const invoke = (op, input) => {
|
|
9
|
+
codexInput(op, input);
|
|
10
|
+
return call(op, input);
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
start: (input) => invoke("start", input),
|
|
14
|
+
status: (id) => invoke("status", { id }),
|
|
15
|
+
cancel: (id) => invoke("cancel", { id })
|
|
16
|
+
};
|
|
17
|
+
}
|
package/dist/sdk/protocol.d.ts
CHANGED
|
@@ -34,7 +34,13 @@ export declare function reference<I, O>(path: string, kind: FunctionKind): Funct
|
|
|
34
34
|
/** Lossless JSON only: no implicit Date, undefined, NaN or prototype coercion. */
|
|
35
35
|
export declare function stableJson(value: unknown): string;
|
|
36
36
|
export declare function boundedJson(value: unknown, maxBytes: number): string;
|
|
37
|
+
export type McpTool = {
|
|
38
|
+
name: string;
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
export declare function assertMcpTools(functions: Record<string, FunctionContract>): void;
|
|
37
42
|
export interface FunctionContract {
|
|
43
|
+
mcp?: McpTool;
|
|
38
44
|
kind: FunctionKind;
|
|
39
45
|
auth: "required" | "public" | "internal";
|
|
40
46
|
input: Record<string, unknown>;
|
package/dist/sdk/protocol.js
CHANGED
|
@@ -74,3 +74,15 @@ export function boundedJson(value, maxBytes) {
|
|
|
74
74
|
throw new FlareError("TOO_LARGE", "JSON payload exceeds limit");
|
|
75
75
|
return json;
|
|
76
76
|
}
|
|
77
|
+
export function assertMcpTools(functions) {
|
|
78
|
+
const names = new Set;
|
|
79
|
+
for (const fn of Object.values(functions)) {
|
|
80
|
+
if (fn.mcp === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
const m = fn.mcp;
|
|
83
|
+
if (!m || typeof m !== "object" || Object.keys(m).sort().join() !== "description,name" || typeof m.name !== "string" || !/^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(m.name) || typeof m.description !== "string" || !m.description.trim() || m.description.length > 1000 || names.has(m.name) || fn.auth === "internal" || fn.input.type !== "object")
|
|
84
|
+
throw new FlareError("INVALID_CONTRACT", "Invalid MCP tool contract");
|
|
85
|
+
boundedJson({ mcp: m, input: fn.input }, 16384);
|
|
86
|
+
names.add(m.name);
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/sdk/react.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ export declare function FlareProvider({ client, children, }: {
|
|
|
9
9
|
query: <I, O>(ref: FunctionRef<I, O>, args: I) => Promise<O>;
|
|
10
10
|
mutation: <I, O>(ref: FunctionRef<I, O>, args: I) => Promise<O>;
|
|
11
11
|
action: <I, O>(ref: FunctionRef<I, O>, args: I) => Promise<O>;
|
|
12
|
+
codexEvents<I>(ref: FunctionRef<I, import("./codex-stream.js").CodexSubscription>, args: I, streamOptions?: {
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
after?: number;
|
|
15
|
+
}): AsyncGenerator<import("./codex-stream.js").CodexEvent, void, any>;
|
|
12
16
|
onUpdate<I, O>(ref: FunctionRef<I, O>, args: I, next: (value: O) => void, error?: (error: FlareError) => void): () => void;
|
|
13
17
|
refreshAuth(): void;
|
|
14
18
|
dispose(): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Json } from "./protocol.js";
|
|
2
|
+
export type SearchState = {
|
|
3
|
+
fileId: string;
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
status: "excluded" | "pending" | "sending" | "queued" | "running" | "completed" | "error" | "skipped" | "outdated" | "deleting";
|
|
6
|
+
error: string | null;
|
|
7
|
+
};
|
|
8
|
+
export type SearchHit = {
|
|
9
|
+
fileId: string;
|
|
10
|
+
name: string;
|
|
11
|
+
text: string;
|
|
12
|
+
score: number;
|
|
13
|
+
};
|
|
14
|
+
export interface SearchContext {
|
|
15
|
+
index(fileId: string): Promise<SearchState>;
|
|
16
|
+
exclude(fileId: string): Promise<SearchState>;
|
|
17
|
+
status(fileId: string): Promise<SearchState>;
|
|
18
|
+
query(input: {
|
|
19
|
+
query: string;
|
|
20
|
+
limit?: number;
|
|
21
|
+
fileIds?: string[];
|
|
22
|
+
}, authorize: (fileIds: string[]) => Promise<string[]>): Promise<SearchHit[]>;
|
|
23
|
+
}
|
|
24
|
+
/** Runs in the trusted action, never in browser code. ACLs remain application data. */
|
|
25
|
+
export declare function searchContext(command: (op: string, input: Json) => Promise<any>): SearchContext;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FlareError } from "./protocol.js";
|
|
2
|
+
export function searchContext(command) {
|
|
3
|
+
return {
|
|
4
|
+
index: (fileId) => command("index", { fileId }),
|
|
5
|
+
exclude: (fileId) => command("exclude", { fileId }),
|
|
6
|
+
status: (fileId) => command("status", { fileId }),
|
|
7
|
+
async query(input, authorize) {
|
|
8
|
+
if (typeof authorize !== "function")
|
|
9
|
+
throw new FlareError("FORBIDDEN", "A current file access check is required");
|
|
10
|
+
const hits = await command("query", input);
|
|
11
|
+
if (!hits.length)
|
|
12
|
+
return [];
|
|
13
|
+
const ids = [...new Set(hits.map((h) => h.fileId))];
|
|
14
|
+
const allowed = await authorize(ids);
|
|
15
|
+
if (!Array.isArray(allowed) || allowed.some((id) => !ids.includes(id)))
|
|
16
|
+
throw new FlareError("FORBIDDEN", "Invalid file access result");
|
|
17
|
+
const current = await command("validate", { fileIds: allowed });
|
|
18
|
+
const visible = new Set(current);
|
|
19
|
+
return hits.filter((h) => visible.has(h.fileId));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
package/dist/sdk/server.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export type { CodexSubscription } from "./codex-stream.js";
|
|
2
|
+
import type { CodexContext } from "./codex.js";
|
|
3
|
+
export type { CodexContext, CodexTask, CodexTaskInput } from "./codex.js";
|
|
4
|
+
import type { SearchContext } from "./search.js";
|
|
5
|
+
export type { SearchContext, SearchHit, SearchState } from "./search.js";
|
|
1
6
|
export { z } from "zod";
|
|
2
7
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
8
|
import type { Validator, Schema } from "./schema.js";
|
|
@@ -5,7 +10,7 @@ import type { ReadDatabase, WriteDatabase } from "./database.js";
|
|
|
5
10
|
export { defineSchema, defineTable, schemaContract } from "./schema.js";
|
|
6
11
|
export type { Validator, Schema, Doc, Value, Input, SchemaContract, } from "./schema.js";
|
|
7
12
|
export type { ReadDatabase, WriteDatabase, Page } from "./database.js";
|
|
8
|
-
import { type Auth, type FunctionKind, type FunctionRef, type Json, type Manifest } from "./protocol.js";
|
|
13
|
+
import { type McpTool, type Auth, type FunctionKind, type FunctionRef, type Json, type Manifest } from "./protocol.js";
|
|
9
14
|
export interface QueryContext<S extends Schema = Schema> {
|
|
10
15
|
readonly db: ReadDatabase<S>;
|
|
11
16
|
readonly auth: Auth | null;
|
|
@@ -21,6 +26,8 @@ export interface MutationContext<S extends Schema = Schema> {
|
|
|
21
26
|
};
|
|
22
27
|
}
|
|
23
28
|
export interface ActionContext {
|
|
29
|
+
readonly codex: CodexContext;
|
|
30
|
+
readonly search: SearchContext;
|
|
24
31
|
readonly auth: Auth | null;
|
|
25
32
|
runQuery<I, O>(ref: FunctionRef<I, O>, args: I): Promise<O>;
|
|
26
33
|
runMutation<I, O>(ref: FunctionRef<I, O>, args: I): Promise<O>;
|
|
@@ -86,6 +93,7 @@ export interface ActionContext {
|
|
|
86
93
|
type Context<K extends FunctionKind, S extends Schema = Schema> = K extends "action" ? ActionContext : K extends "mutation" ? MutationContext<S> : QueryContext<S>;
|
|
87
94
|
export interface Definition<K extends FunctionKind = FunctionKind, I = any, O = any, S extends Schema = Schema> {
|
|
88
95
|
readonly kind: K;
|
|
96
|
+
readonly mcp?: McpTool;
|
|
89
97
|
readonly input: Validator<any, I>;
|
|
90
98
|
readonly output: Validator<O, any>;
|
|
91
99
|
readonly auth: "required" | "public" | "internal";
|
|
@@ -95,18 +103,21 @@ export declare const query: <IV extends Validator, OV extends Validator>(options
|
|
|
95
103
|
input: IV;
|
|
96
104
|
output: OV;
|
|
97
105
|
auth?: "required" | "public" | "internal";
|
|
106
|
+
mcp?: McpTool;
|
|
98
107
|
handler: (ctx: QueryContext<Schema>, args: StandardSchemaV1.InferOutput<IV>) => StandardSchemaV1.InferInput<OV> | Promise<StandardSchemaV1.InferInput<OV>>;
|
|
99
108
|
}) => Definition<"query", StandardSchemaV1.InferOutput<IV>, StandardSchemaV1.InferInput<OV>, Schema>;
|
|
100
109
|
export declare const mutation: <IV extends Validator, OV extends Validator>(options: {
|
|
101
110
|
input: IV;
|
|
102
111
|
output: OV;
|
|
103
112
|
auth?: "required" | "public" | "internal";
|
|
113
|
+
mcp?: McpTool;
|
|
104
114
|
handler: (ctx: MutationContext<Schema>, args: StandardSchemaV1.InferOutput<IV>) => StandardSchemaV1.InferInput<OV> | Promise<StandardSchemaV1.InferInput<OV>>;
|
|
105
115
|
}) => Definition<"mutation", StandardSchemaV1.InferOutput<IV>, StandardSchemaV1.InferInput<OV>, Schema>;
|
|
106
116
|
export declare const action: <IV extends Validator, OV extends Validator>(options: {
|
|
107
117
|
input: IV;
|
|
108
118
|
output: OV;
|
|
109
119
|
auth?: "required" | "public" | "internal";
|
|
120
|
+
mcp?: McpTool;
|
|
110
121
|
handler: (ctx: ActionContext, args: StandardSchemaV1.InferOutput<IV>) => StandardSchemaV1.InferInput<OV> | Promise<StandardSchemaV1.InferInput<OV>>;
|
|
111
122
|
}) => Definition<"action", StandardSchemaV1.InferOutput<IV>, StandardSchemaV1.InferInput<OV>, Schema>;
|
|
112
123
|
export declare function createFunctions<S extends Schema>(): {
|
|
@@ -114,18 +125,21 @@ export declare function createFunctions<S extends Schema>(): {
|
|
|
114
125
|
input: IV;
|
|
115
126
|
output: OV;
|
|
116
127
|
auth?: "required" | "public" | "internal";
|
|
128
|
+
mcp?: McpTool;
|
|
117
129
|
handler: (ctx: QueryContext<S>, args: StandardSchemaV1.InferOutput<IV>) => StandardSchemaV1.InferInput<OV> | Promise<StandardSchemaV1.InferInput<OV>>;
|
|
118
130
|
}) => Definition<"query", StandardSchemaV1.InferOutput<IV>, StandardSchemaV1.InferInput<OV>, S>;
|
|
119
131
|
mutation: <IV extends Validator, OV extends Validator>(options: {
|
|
120
132
|
input: IV;
|
|
121
133
|
output: OV;
|
|
122
134
|
auth?: "required" | "public" | "internal";
|
|
135
|
+
mcp?: McpTool;
|
|
123
136
|
handler: (ctx: MutationContext<S>, args: StandardSchemaV1.InferOutput<IV>) => StandardSchemaV1.InferInput<OV> | Promise<StandardSchemaV1.InferInput<OV>>;
|
|
124
137
|
}) => Definition<"mutation", StandardSchemaV1.InferOutput<IV>, StandardSchemaV1.InferInput<OV>, S>;
|
|
125
138
|
action: <IV extends Validator, OV extends Validator>(options: {
|
|
126
139
|
input: IV;
|
|
127
140
|
output: OV;
|
|
128
141
|
auth?: "required" | "public" | "internal";
|
|
142
|
+
mcp?: McpTool;
|
|
129
143
|
handler: (ctx: ActionContext, args: StandardSchemaV1.InferOutput<IV>) => StandardSchemaV1.InferInput<OV> | Promise<StandardSchemaV1.InferInput<OV>>;
|
|
130
144
|
}) => Definition<"action", StandardSchemaV1.InferOutput<IV>, StandardSchemaV1.InferInput<OV>, Schema>;
|
|
131
145
|
};
|
package/dist/sdk/server.js
CHANGED
|
@@ -3,6 +3,7 @@ import { schemaContract } from "./schema.js";
|
|
|
3
3
|
export { defineSchema, defineTable, schemaContract } from "./schema.js";
|
|
4
4
|
import {
|
|
5
5
|
assertPath,
|
|
6
|
+
assertMcpTools,
|
|
6
7
|
boundedJson,
|
|
7
8
|
LIMITS,
|
|
8
9
|
FlareError
|
|
@@ -63,8 +64,15 @@ export function manifest(app) {
|
|
|
63
64
|
});
|
|
64
65
|
if (!input || !output)
|
|
65
66
|
throw new FlareError("INVALID_CONTRACT", `${path} requires JSON Schema converters`);
|
|
66
|
-
functions[path] = {
|
|
67
|
+
functions[path] = {
|
|
68
|
+
kind: fn.kind,
|
|
69
|
+
auth: fn.auth,
|
|
70
|
+
input,
|
|
71
|
+
output,
|
|
72
|
+
...fn.mcp !== undefined ? { mcp: fn.mcp } : {}
|
|
73
|
+
};
|
|
67
74
|
}
|
|
75
|
+
assertMcpTools(functions);
|
|
68
76
|
boundedJson(functions, 1048576);
|
|
69
77
|
return { protocol: 1, runtimeAbi: 4, functions };
|
|
70
78
|
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
async function e(e,t,n){let r=e.getReader(),i=[],a=0;try{for(;;){let{done:e,value:o}=await r.read();if(e)break;a+=o.byteLength,a>t&&n(),i.push(o)}}finally{await r.cancel().catch(()=>{})}if(i.length===1)return i[0];let o=new Uint8Array(a),s=0;for(let e of i)o.set(e,s),s+=e.byteLength;return o}var t=Object.freeze({inputBytes:65536,outputBytes:262144,frameBytes:8192,subscriptions:64,fileBytes:10485760}),n=class extends Error{code;ambiguous;constructor(e,t,n=!1){super(t),this.code=e,this.ambiguous=n,this.name=`FlareError`}};function r(e){if(e.length>256||!/^[A-Za-z][A-Za-z0-9_]*(\/[A-Za-z][A-Za-z0-9_]*)*$/.test(e)||e.split(`/`).some(e=>[`__proto__`,`prototype`,`constructor`].includes(e)))throw new n(`INVALID_PATH`,`Invalid function path`)}function i(e,t){return r(e),Object.freeze({path:e,kind:t})}function a(e){let t=new Set;function r(e,i){if(i>40)throw new n(`INVALID_JSON`,`JSON nesting limit exceeded`);if(e===null||typeof e==`string`||typeof e==`boolean`||typeof e==`number`&&Number.isFinite(e))return e;if(typeof e!=`object`||!e||t.has(e))throw new n(`INVALID_JSON`,`Expected lossless JSON`);if(!Array.isArray(e)&&Object.getPrototypeOf(e)!==Object.prototype&&Object.getPrototypeOf(e)!==null)throw new n(`INVALID_JSON`,`Expected plain JSON object`);if(Object.getOwnPropertySymbols(e).length)throw new n(`INVALID_JSON`,`Symbol keys are unsupported`);t.add(e);let a;if(Array.isArray(e))a=Array.from(e,e=>r(e,i+1));else{let t=Object.create(null);for(let a of Object.keys(e).sort()){let o=Object.getOwnPropertyDescriptor(e,a);if(!(`value`in o))throw new n(`INVALID_JSON`,`Getters are unsupported`);t[a]=r(o.value,i+1)}a=t}return t.delete(e),a}return JSON.stringify(r(e,0))}function o(e,t){let r=a(e);if(new TextEncoder().encode(r).byteLength>t)throw new n(`TOO_LARGE`,`JSON payload exceeds limit`);return r}function s(e){if(e.size>t.fileBytes)throw new n(`TOO_LARGE`,`Maximum file size: 10 MiB`);if(!e.name||e.name.length>120||/[\x00-\x1f\x7f/\\]/.test(e.name))throw new n(`INVALID_INPUT`,`File name must be 1–120 characters, without path separators`);return{name:e.name,size:e.size,type:[`text/plain`,`image/png`,`image/jpeg`,`image/webp`,`application/pdf`].includes(e.type)?e.type:`application/octet-stream`}}async function c(r,i,a={}){if(i.size>t.fileBytes)throw new n(`TOO_LARGE`,`Maximum file size: 10 MiB`);let o=new URL(r.url);if(o.username||o.password||o.protocol!==`https:`&&!(o.protocol===`http:`&&[`localhost`,`127.0.0.1`,`[::1]`].includes(o.hostname)))throw new n(`INVALID_URL`,`Expected secure upload URL`);let s;try{s=await(a.fetch??globalThis.fetch)(o.href,{method:`POST`,body:i,redirect:`error`,credentials:`omit`,signal:AbortSignal.any([...a.signal?[a.signal]:[],AbortSignal.timeout(a.timeoutMs??12e4)])})}catch{throw new n(`UPLOAD_UNKNOWN`,`Connection interrupted or upload timed out; refresh the file list before trying again`,!0)}let c=s.body?await e(s.body,t.outputBytes,()=>{throw Error(`Upload response exceeds limit`)}).then(e=>JSON.parse(new TextDecoder(`utf-8`,{fatal:!0}).decode(e))).catch(()=>null):null;if(!s.ok){let e=c&&typeof c==`object`&&`error`in c&&typeof c.error==`string`?c.error:`HTTP_${s.status}`;throw new n(e,`Upload failed: ${e}`,s.status>=500)}if(!c||typeof c!=`object`||!(`id`in c)||typeof c.id!=`string`)throw new n(`INVALID_RESPONSE`,`Upload outcome is unknown; refresh the file list before uploading again`,!0);return{id:c.id}}var l=new Set([`UNAUTHORIZED`,`AUTH_EXPIRED`,`FORBIDDEN`,`AUTH_DISABLED`,`HTTP_401`,`HTTP_403`]);function u(i){let s=new URL(i.url);if(!/^https?:$/.test(s.protocol)||s.username||s.password||s.search||s.hash)throw new n(`INVALID_URL`,`Expected backend HTTP URL`);if(s.protocol===`http:`&&![`localhost`,`127.0.0.1`,`[::1]`].includes(s.hostname))throw new n(`INVALID_URL`,`Remote backends require HTTPS`);s.pathname=s.pathname.replace(/\/$/,``);let c=i.fetch??globalThis.fetch;if(!c)throw new n(`TRANSPORT_UNAVAILABLE`,`Provide a fetch implementation`);let u=new Map,d=!1,f,p=!1,m=0,h=0,g=0,_,v=new Set,y=e=>`${s.href.replace(/\/$/,``)}${e}`,b=(e,t,n)=>{for(let r of e.observers)try{r[t]?.(t===`next`?JSON.parse(n):n)}catch{}};function x(){if(d)throw new n(`DISPOSED`,`Client is disposed`)}async function S(r,a,s){x();let l=o(a,t.inputBytes),u;try{u=await i.getToken?.()??null}catch{throw new n(`AUTH_UNAVAILABLE`,`Cannot obtain access token`)}x();let d=new AbortController,f=setTimeout(()=>d.abort(),i.requestTimeoutMs??3e4);try{let i=await c(y(r),{method:`POST`,headers:{"Content-Type":`application/json`,...u?{Authorization:`Bearer ${u}`}:{}},body:l,signal:d.signal,redirect:`error`});if(!i.body)throw new n(`INVALID_RESPONSE`,`Missing response body`,s);let a=await e(i.body,t.outputBytes,()=>{throw new n(`INVALID_RESPONSE`,`Response exceeds limit`,s)}),f;try{f=JSON.parse(new TextDecoder(`utf-8`,{fatal:!0}).decode(a))}catch(e){throw i.ok?e:new n(`HTTP_${i.status}`,`Backend request failed`,s&&i.status>=500)}if(!i.ok)throw new n(f&&typeof f==`object`&&`error`in f&&typeof f.error==`string`&&/^[A-Z][A-Z0-9_]{0,63}$/.test(f.error)?f.error:`HTTP_${i.status}`,`Backend request failed`,s&&i.status>=500);if(!f||typeof f!=`object`||!(`result`in f))throw new n(`INVALID_RESPONSE`,`Invalid protocol envelope`,s);return o(f.result,t.outputBytes),f.result}catch(e){throw e instanceof n?e:e instanceof SyntaxError?new n(`INVALID_RESPONSE`,`Invalid JSON response`,s):new n(`TRANSPORT_ERROR`,s?`Write outcome is unknown; do not replay automatically`:`Backend request interrupted`,s)}finally{clearTimeout(f)}}function C(e){f?.send(o(e,t.frameBytes))}function w(e){C({type:`subscribe`,id:e.id,path:e.ref.path,args:e.args})}function T(){if(d||!u.size||_)return;let e=Math.min(3e4,250*2**Math.min(m++,7));_=setTimeout(()=>{_=void 0,E()},e)}async function E(){if(d||p||f||!u.size)return;p=!0;let e=++h;try{let r=await S(`/v1/live/ticket`,{},!1);if(d||e!==h||!u.size)return;if(!r||typeof r.ticket!=`string`||r.ticket.length>1024)throw new n(`INVALID_RESPONSE`,`Invalid live ticket`);let a=new URL(y(`/v1/live`));a.protocol=a.protocol===`https:`?`wss:`:`ws:`,a.searchParams.set(`ticket`,r.ticket);let s=i.createWebSocket?i.createWebSocket(a.href):new globalThis.WebSocket(a.href);f=s;let c=()=>{if(!(f!==s||d)){m=0;for(let e of u.values())e.revision=-1,e.snapshot=void 0,e.terminal||w(e)}},p=e=>{if(!(f!==s||d))try{if(typeof e.data!=`string`||new TextEncoder().encode(e.data).byteLength>t.outputBytes+1024)throw Error(`Invalid frame`);let r=JSON.parse(e.data);if(r.type===`ready`)return;let i=[...u.values()].find(e=>e.id===r.id);if(!i||i.terminal)return;if(r.type===`error`){i.terminal=!0,l.has(r.code)&&(i.snapshot=void 0),b(i,`error`,new n(typeof r.code==`string`?r.code:`LIVE_ERROR`,`Live subscription failed`));return}if(r.type!==`snapshot`||!Number.isSafeInteger(r.revision)||r.revision<0||!(`result`in r))throw Error(`Invalid snapshot`);let a=o(r.result,t.outputBytes);if(r.revision>i.revision){let e=i.snapshot!==a;i.revision=r.revision,i.snapshot=a,e&&b(i,`next`,i.snapshot)}C({type:`ack`,id:i.id,revision:i.revision})}catch{s.close(1002,`Invalid protocol frame`)}},g=e=>{if(x(),f===s){f=void 0;for(let t of u.values())e.code===1008&&(t.snapshot=void 0,t.revision=-1),t.terminal||b(t,`error`,new n(e.code===1008?`AUTH_EXPIRED`:`LIVE_DISCONNECTED`,`Live reconnecting; next snapshot will be current`));T()}},_=()=>s.close(),x=()=>{s.removeEventListener(`open`,c),s.removeEventListener(`message`,p),s.removeEventListener(`close`,g),s.removeEventListener(`error`,_),v.delete(x)};v.add(x),s.addEventListener(`open`,c),s.addEventListener(`message`,p),s.addEventListener(`close`,g),s.addEventListener(`error`,_)}catch(t){if(d||e!==h)return;for(let e of u.values())t instanceof n&&l.has(t.code)&&(e.snapshot=void 0,e.revision=-1),b(e,`error`,t instanceof n?t:new n(`LIVE_UNAVAILABLE`,`Live connection failed`));T()}finally{p=!1,!d&&e!==h&&!f&&u.size&&T()}}function D(e){return async(t,i)=>{if(r(t.path),t.kind!==e)throw new n(`INVALID_KIND`,`Expected a ${e} function`);return S(`/v1/call`,{path:t.path,kind:t.kind,args:i},t.kind===`mutation`||t.kind===`action`)}}return{query:D(`query`),mutation:D(`mutation`),action:D(`action`),onUpdate(e,i,s,c){let l={next:s,error:c};if(x(),r(e.path),e.kind!==`query`)throw new n(`INVALID_KIND`,`Only queries are subscribable`);o({type:`subscribe`,id:`subscription-9999999999`,path:e.path,args:i},t.frameBytes);let d=a({path:e.path,args:i}),p=u.get(d);if(!p){if(u.size>=t.subscriptions)throw new n(`TOO_MANY_SUBSCRIPTIONS`,`Subscription limit exceeded`);p={id:`subscription-${++g}`,ref:e,args:JSON.parse(a(i)),observers:new Set,revision:-1,terminal:!1},u.set(d,p),f?.readyState===1&&w(p)}let m={next:l.next,error:l.error};p.observers.add(m),p.snapshot!==void 0&&b({...p,observers:new Set([l])},`next`,p.snapshot),E();let y=!1;return()=>{if(!y&&(y=!0,p.observers.delete(m),p.observers.size||(u.delete(d),f?.readyState===1&&C({type:`unsubscribe`,id:p.id})),!u.size)){++h,_&&clearTimeout(_),_=void 0;let e=f;f=void 0;for(let e of[...v])e();e?.close(1e3,`No subscriptions`)}}},refreshAuth(){x(),++h,_&&clearTimeout(_),_=void 0;for(let e of[...v])e();let e=f;f=void 0,e?.close(1e3,`Authorization changed`);for(let e of u.values())e.snapshot=void 0,e.revision=-1,e.terminal=!1,b(e,`error`,new n(`AUTH_CHANGED`,`Authorization changed; cached value invalidated`));E()},dispose(){if(!d){d=!0,++h,_&&clearTimeout(_),_=void 0;for(let e of[...v])e();u.clear(),f?.close(1e3,`Disposed`),f=void 0}}}}export{i,s as n,c as r,u as t};
|