@agentic-kit/run-log-client 0.2.0
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 +23 -0
- package/README.md +82 -0
- package/client.d.ts +185 -0
- package/client.js +291 -0
- package/documents.d.ts +35 -0
- package/documents.js +117 -0
- package/esm/client.d.ts +185 -0
- package/esm/client.js +284 -0
- package/esm/documents.d.ts +35 -0
- package/esm/documents.js +107 -0
- package/esm/follow-run.d.ts +38 -0
- package/esm/follow-run.js +91 -0
- package/esm/index.d.ts +22 -0
- package/esm/index.js +21 -0
- package/esm/local-run.d.ts +49 -0
- package/esm/local-run.js +69 -0
- package/esm/names.d.ts +47 -0
- package/esm/names.js +69 -0
- package/esm/realtime.d.ts +68 -0
- package/esm/realtime.js +89 -0
- package/esm/surface.d.ts +17 -0
- package/esm/surface.js +21 -0
- package/esm/transport.d.ts +23 -0
- package/esm/transport.js +42 -0
- package/esm/view.d.ts +52 -0
- package/esm/view.js +59 -0
- package/follow-run.d.ts +38 -0
- package/follow-run.js +94 -0
- package/index.d.ts +22 -0
- package/index.js +60 -0
- package/local-run.d.ts +49 -0
- package/local-run.js +76 -0
- package/names.d.ts +47 -0
- package/names.js +73 -0
- package/package.json +42 -0
- package/realtime.d.ts +68 -0
- package/realtime.js +95 -0
- package/surface.d.ts +17 -0
- package/surface.js +24 -0
- package/transport.d.ts +23 -0
- package/transport.js +45 -0
- package/view.d.ts +52 -0
- package/view.js +68 -0
package/esm/surface.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling "this database has no run surface" apart from "the read failed".
|
|
3
|
+
*
|
|
4
|
+
* A database whose agent module was provisioned without `has_runs` has no
|
|
5
|
+
* `agent_run`/`agent_event` at all, so the schema rejects the query outright
|
|
6
|
+
* rather than returning zero rows. That is a configuration fact about the
|
|
7
|
+
* database, not a failure of the read, and a raw GraphQL validation dump is a
|
|
8
|
+
* poor way to tell an operator so. The error is still an error — it is only
|
|
9
|
+
* classified here, never swallowed.
|
|
10
|
+
*/
|
|
11
|
+
import type { RunLogNames } from './names';
|
|
12
|
+
/**
|
|
13
|
+
* Matched on the validation messages the tenant API returns, because the absence
|
|
14
|
+
* of the tables is only observable through the schema: the run collection is not
|
|
15
|
+
* a field and the run filter is not a type.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isMissingRunSurface(error: unknown, names: RunLogNames): boolean;
|
package/esm/surface.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling "this database has no run surface" apart from "the read failed".
|
|
3
|
+
*
|
|
4
|
+
* A database whose agent module was provisioned without `has_runs` has no
|
|
5
|
+
* `agent_run`/`agent_event` at all, so the schema rejects the query outright
|
|
6
|
+
* rather than returning zero rows. That is a configuration fact about the
|
|
7
|
+
* database, not a failure of the read, and a raw GraphQL validation dump is a
|
|
8
|
+
* poor way to tell an operator so. The error is still an error — it is only
|
|
9
|
+
* classified here, never swallowed.
|
|
10
|
+
*/
|
|
11
|
+
const messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
12
|
+
/**
|
|
13
|
+
* Matched on the validation messages the tenant API returns, because the absence
|
|
14
|
+
* of the tables is only observable through the schema: the run collection is not
|
|
15
|
+
* a field and the run filter is not a type.
|
|
16
|
+
*/
|
|
17
|
+
export function isMissingRunSurface(error, names) {
|
|
18
|
+
const message = messageOf(error);
|
|
19
|
+
return (message.includes(`Cannot query field "${names.runs}"`) ||
|
|
20
|
+
message.includes(`Unknown type "${names.runType}Filter"`));
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GraphQL transport, kept behind one function so the store never knows how
|
|
3
|
+
* a host authenticates.
|
|
4
|
+
*
|
|
5
|
+
* The run surface lives in the tenant's own database, so this addresses the
|
|
6
|
+
* tenant API endpoint the platform UI already resolves per database, with the
|
|
7
|
+
* user's bearer token — the reader is the user, RLS decides what a run shows,
|
|
8
|
+
* and no service credential is involved anywhere in this lane.
|
|
9
|
+
*/
|
|
10
|
+
export type GraphqlRequest = <T>(query: string, variables?: Record<string, unknown>) => Promise<T>;
|
|
11
|
+
export interface GraphqlTransportOptions {
|
|
12
|
+
endpoint: string;
|
|
13
|
+
/** Resolved per call, so a rotated token is picked up without rebuilding. */
|
|
14
|
+
headers?: () => Record<string, string>;
|
|
15
|
+
fetch?: typeof globalThis.fetch;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A GraphQL error is thrown, never folded into an empty page: a transcript that
|
|
20
|
+
* failed to load and a run with no events look identical to a renderer, and the
|
|
21
|
+
* second is a lie a user cannot detect.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createGraphqlRequest(options: GraphqlTransportOptions): GraphqlRequest;
|
package/esm/transport.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GraphQL transport, kept behind one function so the store never knows how
|
|
3
|
+
* a host authenticates.
|
|
4
|
+
*
|
|
5
|
+
* The run surface lives in the tenant's own database, so this addresses the
|
|
6
|
+
* tenant API endpoint the platform UI already resolves per database, with the
|
|
7
|
+
* user's bearer token — the reader is the user, RLS decides what a run shows,
|
|
8
|
+
* and no service credential is involved anywhere in this lane.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* A GraphQL error is thrown, never folded into an empty page: a transcript that
|
|
12
|
+
* failed to load and a run with no events look identical to a renderer, and the
|
|
13
|
+
* second is a lie a user cannot detect.
|
|
14
|
+
*/
|
|
15
|
+
export function createGraphqlRequest(options) {
|
|
16
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
17
|
+
if (!doFetch) {
|
|
18
|
+
throw new Error('run-log-client needs a fetch implementation');
|
|
19
|
+
}
|
|
20
|
+
return async (query, variables) => {
|
|
21
|
+
const res = await doFetch(options.endpoint, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
...(options.headers ? options.headers() : {}),
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify({ query, variables: variables ?? {} }),
|
|
28
|
+
signal: options.signal,
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
throw new Error(`run log request failed: ${res.status} ${await res.text()}`);
|
|
32
|
+
}
|
|
33
|
+
const body = (await res.json());
|
|
34
|
+
if (body.errors && body.errors.length > 0) {
|
|
35
|
+
throw new Error(`run log request failed: ${body.errors.map((e) => e.message).join('; ')}`);
|
|
36
|
+
}
|
|
37
|
+
if (!body.data) {
|
|
38
|
+
throw new Error('run log request returned no data');
|
|
39
|
+
}
|
|
40
|
+
return body.data;
|
|
41
|
+
};
|
|
42
|
+
}
|
package/esm/view.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run view: everything a surface draws, derived from the log and nothing
|
|
3
|
+
* else.
|
|
4
|
+
*
|
|
5
|
+
* Kept as a pure reducer over records so the rendering layer holds no
|
|
6
|
+
* transcript logic of its own — the same reduction runs in a React panel, a
|
|
7
|
+
* test, or a CLI tail. Records are merged by `seq` rather than appended, which
|
|
8
|
+
* is what makes a reconnect safe: catching up re-reads a window that may overlap
|
|
9
|
+
* what is already held, and an overlapping read must be idempotent rather than
|
|
10
|
+
* duplicating turns.
|
|
11
|
+
*/
|
|
12
|
+
import { type Conversation, type RunEventRecord, type RunLogCursor, type RunUsage, type ToolStateProjection, type TranscriptReaderRegistry } from '@agentic-kit/run-log';
|
|
13
|
+
import { type RunSummary } from './client';
|
|
14
|
+
export type RunViewPhase = 'loading' | 'live' | 'terminal' | 'error';
|
|
15
|
+
export interface RunView {
|
|
16
|
+
run?: RunSummary;
|
|
17
|
+
/** Records held, by ascending `seq`, without duplicates. */
|
|
18
|
+
records: RunEventRecord[];
|
|
19
|
+
cursor: RunLogCursor;
|
|
20
|
+
conversation: Conversation;
|
|
21
|
+
tools: ToolStateProjection;
|
|
22
|
+
usage: RunUsage;
|
|
23
|
+
phase: RunViewPhase;
|
|
24
|
+
/** Set when a read or a subscription failed; never hidden behind an empty view. */
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Which transcript formats this view can read. Absent means the default
|
|
29
|
+
* registry, so a surface that only ever sees one format configures nothing;
|
|
30
|
+
* a host wired to a second harness passes its registry and the same reducers
|
|
31
|
+
* project its records.
|
|
32
|
+
*/
|
|
33
|
+
export interface RunViewProjection {
|
|
34
|
+
readers?: TranscriptReaderRegistry;
|
|
35
|
+
}
|
|
36
|
+
export declare const emptyRunView: () => RunView;
|
|
37
|
+
/**
|
|
38
|
+
* Merge by `seq`: a later read of the same position replaces the earlier copy
|
|
39
|
+
* (the row is immutable, so they are the same record), and order is by position
|
|
40
|
+
* rather than arrival.
|
|
41
|
+
*/
|
|
42
|
+
export declare function mergeRecords(existing: readonly RunEventRecord[], incoming: readonly RunEventRecord[]): RunEventRecord[];
|
|
43
|
+
export declare function applyRecords(view: RunView, incoming: readonly RunEventRecord[], projection?: RunViewProjection): RunView;
|
|
44
|
+
export declare function applyRun(view: RunView, run: RunSummary, projection?: RunViewProjection): RunView;
|
|
45
|
+
/**
|
|
46
|
+
* A failed read is a visible error, not an empty transcript: a surface that
|
|
47
|
+
* cannot reach the log must say so, because "no events yet" and "I could not
|
|
48
|
+
* read your events" look identical to a user otherwise.
|
|
49
|
+
*/
|
|
50
|
+
export declare function applyError(view: RunView, error: unknown, projection?: RunViewProjection): RunView;
|
|
51
|
+
/** True while the run may still append — the only reason to keep following. */
|
|
52
|
+
export declare const isFollowable: (view: RunView) => boolean;
|
package/esm/view.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run view: everything a surface draws, derived from the log and nothing
|
|
3
|
+
* else.
|
|
4
|
+
*
|
|
5
|
+
* Kept as a pure reducer over records so the rendering layer holds no
|
|
6
|
+
* transcript logic of its own — the same reduction runs in a React panel, a
|
|
7
|
+
* test, or a CLI tail. Records are merged by `seq` rather than appended, which
|
|
8
|
+
* is what makes a reconnect safe: catching up re-reads a window that may overlap
|
|
9
|
+
* what is already held, and an overlapping read must be idempotent rather than
|
|
10
|
+
* duplicating turns.
|
|
11
|
+
*/
|
|
12
|
+
import { projectParts, projectToolState, projectUsage, START, } from '@agentic-kit/run-log';
|
|
13
|
+
import { isTerminalStatus } from './client';
|
|
14
|
+
const project = (records, run, phase, error, projection = {}) => ({
|
|
15
|
+
run,
|
|
16
|
+
records,
|
|
17
|
+
cursor: records.length > 0
|
|
18
|
+
? { afterSeq: records[records.length - 1].seq }
|
|
19
|
+
: START,
|
|
20
|
+
conversation: projectParts(records, projection),
|
|
21
|
+
tools: projectToolState(records, projection),
|
|
22
|
+
usage: projectUsage(records, projection),
|
|
23
|
+
phase,
|
|
24
|
+
error,
|
|
25
|
+
});
|
|
26
|
+
export const emptyRunView = () => project([], undefined, 'loading');
|
|
27
|
+
/**
|
|
28
|
+
* Merge by `seq`: a later read of the same position replaces the earlier copy
|
|
29
|
+
* (the row is immutable, so they are the same record), and order is by position
|
|
30
|
+
* rather than arrival.
|
|
31
|
+
*/
|
|
32
|
+
export function mergeRecords(existing, incoming) {
|
|
33
|
+
if (incoming.length === 0)
|
|
34
|
+
return [...existing];
|
|
35
|
+
const bySeq = new Map();
|
|
36
|
+
for (const record of existing)
|
|
37
|
+
bySeq.set(record.seq, record);
|
|
38
|
+
for (const record of incoming)
|
|
39
|
+
bySeq.set(record.seq, record);
|
|
40
|
+
return [...bySeq.values()].sort((a, b) => a.seq - b.seq);
|
|
41
|
+
}
|
|
42
|
+
const phaseFor = (run) => run && isTerminalStatus(run.status) ? 'terminal' : 'live';
|
|
43
|
+
export function applyRecords(view, incoming, projection) {
|
|
44
|
+
const records = mergeRecords(view.records, incoming);
|
|
45
|
+
return project(records, view.run, phaseFor(view.run), view.error, projection);
|
|
46
|
+
}
|
|
47
|
+
export function applyRun(view, run, projection) {
|
|
48
|
+
return project(view.records, run, phaseFor(run), view.error, projection);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A failed read is a visible error, not an empty transcript: a surface that
|
|
52
|
+
* cannot reach the log must say so, because "no events yet" and "I could not
|
|
53
|
+
* read your events" look identical to a user otherwise.
|
|
54
|
+
*/
|
|
55
|
+
export function applyError(view, error, projection) {
|
|
56
|
+
return project(view.records, view.run, 'error', error instanceof Error ? error.message : String(error), projection);
|
|
57
|
+
}
|
|
58
|
+
/** True while the run may still append — the only reason to keep following. */
|
|
59
|
+
export const isFollowable = (view) => view.phase === 'live' || view.phase === 'loading';
|
package/follow-run.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The follower a run surface subscribes to: one view per change, until the run
|
|
3
|
+
* is terminal or the caller aborts.
|
|
4
|
+
*
|
|
5
|
+
* This is a loop of its own rather than `@agentic-kit/run-log`'s `follow`
|
|
6
|
+
* because a surface follows two things, not one: the log *and* the run row.
|
|
7
|
+
* Status, the attached execution and the head commit live on the row, and a run
|
|
8
|
+
* that goes idle or finishes emits no event announcing it — so a follower that
|
|
9
|
+
* only tailed events would show a finished run as still running.
|
|
10
|
+
*
|
|
11
|
+
* Catch-up needs no special path. Every read is `seq > cursor`, so a client that
|
|
12
|
+
* was asleep for an hour resumes with the same call it makes when live, and the
|
|
13
|
+
* view merges by `seq` so an overlapping read cannot duplicate a turn.
|
|
14
|
+
*/
|
|
15
|
+
import type { RunLogCursor } from '@agentic-kit/run-log';
|
|
16
|
+
import type { GraphqlRunLogClient } from './client';
|
|
17
|
+
import { type RunView } from './view';
|
|
18
|
+
export interface FollowRunOptions {
|
|
19
|
+
cursor?: RunLogCursor;
|
|
20
|
+
/** Delay between reads when no wakeup arrives. */
|
|
21
|
+
pollIntervalMs?: number;
|
|
22
|
+
/** Records per read; a backlog is drained in pages, not in one query. */
|
|
23
|
+
limit?: number;
|
|
24
|
+
/** From `createRealtimeWakeup`, when the app has DataRealtime on the table. */
|
|
25
|
+
waitForChange?: (signal?: AbortSignal) => Promise<void>;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Reads after a terminal status, so the tail written as a run finishes is not
|
|
30
|
+
* lost to a race between the last append and the status update.
|
|
31
|
+
*/
|
|
32
|
+
drainReads?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Yield a view whenever the run changes. The first yield is the run's whole
|
|
36
|
+
* history, so a caller renders once rather than animating a backlog.
|
|
37
|
+
*/
|
|
38
|
+
export declare function followRun(client: GraphqlRunLogClient, runId: string, options?: FollowRunOptions): AsyncGenerator<RunView, void, void>;
|
package/follow-run.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The follower a run surface subscribes to: one view per change, until the run
|
|
4
|
+
* is terminal or the caller aborts.
|
|
5
|
+
*
|
|
6
|
+
* This is a loop of its own rather than `@agentic-kit/run-log`'s `follow`
|
|
7
|
+
* because a surface follows two things, not one: the log *and* the run row.
|
|
8
|
+
* Status, the attached execution and the head commit live on the row, and a run
|
|
9
|
+
* that goes idle or finishes emits no event announcing it — so a follower that
|
|
10
|
+
* only tailed events would show a finished run as still running.
|
|
11
|
+
*
|
|
12
|
+
* Catch-up needs no special path. Every read is `seq > cursor`, so a client that
|
|
13
|
+
* was asleep for an hour resumes with the same call it makes when live, and the
|
|
14
|
+
* view merges by `seq` so an overlapping read cannot duplicate a turn.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.followRun = followRun;
|
|
18
|
+
const run_log_1 = require("@agentic-kit/run-log");
|
|
19
|
+
const view_1 = require("./view");
|
|
20
|
+
const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
21
|
+
const DEFAULT_LIMIT = 200;
|
|
22
|
+
const DEFAULT_DRAIN_READS = 1;
|
|
23
|
+
const defaultSleep = (ms, signal) => new Promise((resolve) => {
|
|
24
|
+
const timer = setTimeout(resolve, ms);
|
|
25
|
+
signal?.addEventListener('abort', () => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
resolve();
|
|
28
|
+
}, { once: true });
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Yield a view whenever the run changes. The first yield is the run's whole
|
|
32
|
+
* history, so a caller renders once rather than animating a backlog.
|
|
33
|
+
*/
|
|
34
|
+
async function* followRun(client, runId, options = {}) {
|
|
35
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
36
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
37
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
38
|
+
const drainReads = options.drainReads ?? DEFAULT_DRAIN_READS;
|
|
39
|
+
// The formats this client can read are the formats the view can project: a
|
|
40
|
+
// surface following a run written by a second harness renders it with no
|
|
41
|
+
// configuration of its own.
|
|
42
|
+
const projection = { readers: client.readers };
|
|
43
|
+
let view = (0, view_1.emptyRunView)();
|
|
44
|
+
let cursor = options.cursor ?? run_log_1.START;
|
|
45
|
+
let drainsLeft = drainReads;
|
|
46
|
+
while (!options.signal?.aborted) {
|
|
47
|
+
let batch = [];
|
|
48
|
+
try {
|
|
49
|
+
view = (0, view_1.applyRun)(view, await client.getRun(runId), projection);
|
|
50
|
+
// Drain the backlog before yielding: a run with a thousand events should
|
|
51
|
+
// render once, not a thousand times.
|
|
52
|
+
for (;;) {
|
|
53
|
+
const page = await client.read(runId, cursor, limit);
|
|
54
|
+
if (page.records.length === 0)
|
|
55
|
+
break;
|
|
56
|
+
cursor = page.cursor;
|
|
57
|
+
batch = batch.concat(page.records);
|
|
58
|
+
if (page.records.length < limit)
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// Yielded, not thrown: a surface that loses the API for a moment should
|
|
64
|
+
// show that it did, and the caller decides whether to abort.
|
|
65
|
+
yield (0, view_1.applyError)(view, error, projection);
|
|
66
|
+
if (options.signal?.aborted)
|
|
67
|
+
return;
|
|
68
|
+
await sleep(pollIntervalMs, options.signal);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// A successful pass clears a previous failure: the surface is reading again.
|
|
72
|
+
view = (0, view_1.applyRecords)({ ...view, error: undefined }, batch, projection);
|
|
73
|
+
yield view;
|
|
74
|
+
if (!(0, view_1.isFollowable)(view)) {
|
|
75
|
+
if (drainsLeft <= 0)
|
|
76
|
+
return;
|
|
77
|
+
drainsLeft -= 1;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
drainsLeft = drainReads;
|
|
81
|
+
}
|
|
82
|
+
if (options.signal?.aborted)
|
|
83
|
+
return;
|
|
84
|
+
if (options.waitForChange) {
|
|
85
|
+
await Promise.race([
|
|
86
|
+
options.waitForChange(options.signal),
|
|
87
|
+
sleep(pollIntervalMs, options.signal),
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
await sleep(pollIntervalMs, options.signal);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agentic-kit/run-log-client` — the run log as a user's browser sees it.
|
|
3
|
+
*
|
|
4
|
+
* One tenant-GraphQL `RunLogStore` over a module's `agent_run`/`agent_event`
|
|
5
|
+
* pair, a follower that tails the run row alongside the log, and the pure
|
|
6
|
+
* reduction a surface renders. No React, no node builtins: the platform UI, a
|
|
7
|
+
* desktop pane and a test all consume the same three pieces.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The projections and record types a surface renders, re-exported so a host
|
|
11
|
+
* imports one package rather than reaching past this one into the kernel.
|
|
12
|
+
*/
|
|
13
|
+
export { ATTACHABLE_STATUSES, createRunLogClient, type CreateRunInput, type CreateThreadInput, GraphqlRunLogClient, isSeqConflict, isTerminalStatus, type ListRunsArgs, type RunLogClientOptions, type RunPatch, type RunStatus, type RunSummary, TERMINAL_STATUSES, type ThreadSummary, } from './client';
|
|
14
|
+
export { advanceRunDocument, appendDocument, createRunDocument, createThreadDocument, EVENT_FIELDS, eventsDocument, RUN_FIELDS, runDocument, runsDocument, } from './documents';
|
|
15
|
+
export { followRun, type FollowRunOptions } from './follow-run';
|
|
16
|
+
export { adoptLocalRun, type LocalRun, markLocalRunFailed, markLocalRunIdle, markLocalRunRunning, openLocalRun, type OpenLocalRunOptions, } from './local-run';
|
|
17
|
+
export { DEFAULT_EVENT_TABLE, DEFAULT_RUN_TABLE, DEFAULT_THREAD_TABLE, type RunLogNames, runLogNames, } from './names';
|
|
18
|
+
export { changedSubscriptionDocument, changedSubscriptionField, createRealtimeWakeup, type RealtimeWakeup, type RealtimeWakeupOptions, type RunLogWsClient, } from './realtime';
|
|
19
|
+
export { isMissingRunSurface } from './surface';
|
|
20
|
+
export { createGraphqlRequest, type GraphqlRequest, type GraphqlTransportOptions, } from './transport';
|
|
21
|
+
export { applyError, applyRecords, applyRun, emptyRunView, isFollowable, mergeRecords, type RunView, type RunViewPhase, type RunViewProjection, } from './view';
|
|
22
|
+
export type { ApprovalResolutionInput, ApprovalState, Conversation, ConversationPart, ModelUsage, RunEventRecord, RunLogCursor, RunLogPage, RunUsage, ToolCallState, ToolCallStatus, ToolStateProjection, } from '@agentic-kit/run-log';
|
package/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@agentic-kit/run-log-client` — the run log as a user's browser sees it.
|
|
4
|
+
*
|
|
5
|
+
* One tenant-GraphQL `RunLogStore` over a module's `agent_run`/`agent_event`
|
|
6
|
+
* pair, a follower that tails the run row alongside the log, and the pure
|
|
7
|
+
* reduction a surface renders. No React, no node builtins: the platform UI, a
|
|
8
|
+
* desktop pane and a test all consume the same three pieces.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.mergeRecords = exports.isFollowable = exports.emptyRunView = exports.applyRun = exports.applyRecords = exports.applyError = exports.createGraphqlRequest = exports.isMissingRunSurface = exports.createRealtimeWakeup = exports.changedSubscriptionField = exports.changedSubscriptionDocument = exports.runLogNames = exports.DEFAULT_THREAD_TABLE = exports.DEFAULT_RUN_TABLE = exports.DEFAULT_EVENT_TABLE = exports.openLocalRun = exports.markLocalRunRunning = exports.markLocalRunIdle = exports.markLocalRunFailed = exports.adoptLocalRun = exports.followRun = exports.runsDocument = exports.runDocument = exports.RUN_FIELDS = exports.eventsDocument = exports.EVENT_FIELDS = exports.createThreadDocument = exports.createRunDocument = exports.appendDocument = exports.advanceRunDocument = exports.TERMINAL_STATUSES = exports.isTerminalStatus = exports.isSeqConflict = exports.GraphqlRunLogClient = exports.createRunLogClient = exports.ATTACHABLE_STATUSES = void 0;
|
|
12
|
+
/**
|
|
13
|
+
* The projections and record types a surface renders, re-exported so a host
|
|
14
|
+
* imports one package rather than reaching past this one into the kernel.
|
|
15
|
+
*/
|
|
16
|
+
var client_1 = require("./client");
|
|
17
|
+
Object.defineProperty(exports, "ATTACHABLE_STATUSES", { enumerable: true, get: function () { return client_1.ATTACHABLE_STATUSES; } });
|
|
18
|
+
Object.defineProperty(exports, "createRunLogClient", { enumerable: true, get: function () { return client_1.createRunLogClient; } });
|
|
19
|
+
Object.defineProperty(exports, "GraphqlRunLogClient", { enumerable: true, get: function () { return client_1.GraphqlRunLogClient; } });
|
|
20
|
+
Object.defineProperty(exports, "isSeqConflict", { enumerable: true, get: function () { return client_1.isSeqConflict; } });
|
|
21
|
+
Object.defineProperty(exports, "isTerminalStatus", { enumerable: true, get: function () { return client_1.isTerminalStatus; } });
|
|
22
|
+
Object.defineProperty(exports, "TERMINAL_STATUSES", { enumerable: true, get: function () { return client_1.TERMINAL_STATUSES; } });
|
|
23
|
+
var documents_1 = require("./documents");
|
|
24
|
+
Object.defineProperty(exports, "advanceRunDocument", { enumerable: true, get: function () { return documents_1.advanceRunDocument; } });
|
|
25
|
+
Object.defineProperty(exports, "appendDocument", { enumerable: true, get: function () { return documents_1.appendDocument; } });
|
|
26
|
+
Object.defineProperty(exports, "createRunDocument", { enumerable: true, get: function () { return documents_1.createRunDocument; } });
|
|
27
|
+
Object.defineProperty(exports, "createThreadDocument", { enumerable: true, get: function () { return documents_1.createThreadDocument; } });
|
|
28
|
+
Object.defineProperty(exports, "EVENT_FIELDS", { enumerable: true, get: function () { return documents_1.EVENT_FIELDS; } });
|
|
29
|
+
Object.defineProperty(exports, "eventsDocument", { enumerable: true, get: function () { return documents_1.eventsDocument; } });
|
|
30
|
+
Object.defineProperty(exports, "RUN_FIELDS", { enumerable: true, get: function () { return documents_1.RUN_FIELDS; } });
|
|
31
|
+
Object.defineProperty(exports, "runDocument", { enumerable: true, get: function () { return documents_1.runDocument; } });
|
|
32
|
+
Object.defineProperty(exports, "runsDocument", { enumerable: true, get: function () { return documents_1.runsDocument; } });
|
|
33
|
+
var follow_run_1 = require("./follow-run");
|
|
34
|
+
Object.defineProperty(exports, "followRun", { enumerable: true, get: function () { return follow_run_1.followRun; } });
|
|
35
|
+
var local_run_1 = require("./local-run");
|
|
36
|
+
Object.defineProperty(exports, "adoptLocalRun", { enumerable: true, get: function () { return local_run_1.adoptLocalRun; } });
|
|
37
|
+
Object.defineProperty(exports, "markLocalRunFailed", { enumerable: true, get: function () { return local_run_1.markLocalRunFailed; } });
|
|
38
|
+
Object.defineProperty(exports, "markLocalRunIdle", { enumerable: true, get: function () { return local_run_1.markLocalRunIdle; } });
|
|
39
|
+
Object.defineProperty(exports, "markLocalRunRunning", { enumerable: true, get: function () { return local_run_1.markLocalRunRunning; } });
|
|
40
|
+
Object.defineProperty(exports, "openLocalRun", { enumerable: true, get: function () { return local_run_1.openLocalRun; } });
|
|
41
|
+
var names_1 = require("./names");
|
|
42
|
+
Object.defineProperty(exports, "DEFAULT_EVENT_TABLE", { enumerable: true, get: function () { return names_1.DEFAULT_EVENT_TABLE; } });
|
|
43
|
+
Object.defineProperty(exports, "DEFAULT_RUN_TABLE", { enumerable: true, get: function () { return names_1.DEFAULT_RUN_TABLE; } });
|
|
44
|
+
Object.defineProperty(exports, "DEFAULT_THREAD_TABLE", { enumerable: true, get: function () { return names_1.DEFAULT_THREAD_TABLE; } });
|
|
45
|
+
Object.defineProperty(exports, "runLogNames", { enumerable: true, get: function () { return names_1.runLogNames; } });
|
|
46
|
+
var realtime_1 = require("./realtime");
|
|
47
|
+
Object.defineProperty(exports, "changedSubscriptionDocument", { enumerable: true, get: function () { return realtime_1.changedSubscriptionDocument; } });
|
|
48
|
+
Object.defineProperty(exports, "changedSubscriptionField", { enumerable: true, get: function () { return realtime_1.changedSubscriptionField; } });
|
|
49
|
+
Object.defineProperty(exports, "createRealtimeWakeup", { enumerable: true, get: function () { return realtime_1.createRealtimeWakeup; } });
|
|
50
|
+
var surface_1 = require("./surface");
|
|
51
|
+
Object.defineProperty(exports, "isMissingRunSurface", { enumerable: true, get: function () { return surface_1.isMissingRunSurface; } });
|
|
52
|
+
var transport_1 = require("./transport");
|
|
53
|
+
Object.defineProperty(exports, "createGraphqlRequest", { enumerable: true, get: function () { return transport_1.createGraphqlRequest; } });
|
|
54
|
+
var view_1 = require("./view");
|
|
55
|
+
Object.defineProperty(exports, "applyError", { enumerable: true, get: function () { return view_1.applyError; } });
|
|
56
|
+
Object.defineProperty(exports, "applyRecords", { enumerable: true, get: function () { return view_1.applyRecords; } });
|
|
57
|
+
Object.defineProperty(exports, "applyRun", { enumerable: true, get: function () { return view_1.applyRun; } });
|
|
58
|
+
Object.defineProperty(exports, "emptyRunView", { enumerable: true, get: function () { return view_1.emptyRunView; } });
|
|
59
|
+
Object.defineProperty(exports, "isFollowable", { enumerable: true, get: function () { return view_1.isFollowable; } });
|
|
60
|
+
Object.defineProperty(exports, "mergeRecords", { enumerable: true, get: function () { return view_1.mergeRecords; } });
|
package/local-run.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A run executed by a host on the user's own machine.
|
|
3
|
+
*
|
|
4
|
+
* The cloud lane opens its run on the platform's side — the worker creates the
|
|
5
|
+
* row, then hands the Job a callback lane to append through. A local host has no
|
|
6
|
+
* worker in front of it: it authenticates as the user against the tenant API and
|
|
7
|
+
* so opens its own thread and run, which is why `placement` is stated here
|
|
8
|
+
* rather than defaulted, and why nothing in this file passes an identity. The
|
|
9
|
+
* claims of the request's transaction are the run's owner, and RLS on
|
|
10
|
+
* `agent_run`/`agent_event` is the same visibility rule a cloud run obeys.
|
|
11
|
+
*
|
|
12
|
+
* Kept in this package rather than in the desktop app so the host that opens a
|
|
13
|
+
* local run and the reader the web UI draws it with are the same code.
|
|
14
|
+
*/
|
|
15
|
+
import type { GraphqlRunLogClient, RunSummary } from './client';
|
|
16
|
+
export interface OpenLocalRunOptions {
|
|
17
|
+
store: GraphqlRunLogClient;
|
|
18
|
+
/** Title for the thread this run opens; the first prompt makes a good one. */
|
|
19
|
+
title?: string;
|
|
20
|
+
/** An existing thread to append this run to, when the host already has one. */
|
|
21
|
+
threadId?: string;
|
|
22
|
+
repoUrl?: string;
|
|
23
|
+
branch?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface LocalRun {
|
|
26
|
+
runId: string;
|
|
27
|
+
threadId: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Open a durable local run: a thread if the host does not already have one, then
|
|
31
|
+
* a `pending` run marked `placement: 'local'`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function openLocalRun(options: OpenLocalRunOptions): Promise<LocalRun>;
|
|
34
|
+
/**
|
|
35
|
+
* Adopt a run this host opened earlier — the resume path, and the check that a
|
|
36
|
+
* cached run id still belongs to the signed-in user. Throws when the run is gone
|
|
37
|
+
* or invisible, so a stale cache cannot silently start a second history.
|
|
38
|
+
*/
|
|
39
|
+
export declare function adoptLocalRun(store: GraphqlRunLogClient, runId: string): Promise<RunSummary>;
|
|
40
|
+
/** The host has picked the run up and is about to drive the model. */
|
|
41
|
+
export declare function markLocalRunRunning(store: GraphqlRunLogClient, runId: string, now?: Date): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* The turn finished and the host is waiting for the user. `idle` rather than
|
|
44
|
+
* `succeeded` for the same reason a cloud chat run goes idle: the session can be
|
|
45
|
+
* prompted again, and only a closed session is terminal.
|
|
46
|
+
*/
|
|
47
|
+
export declare function markLocalRunIdle(store: GraphqlRunLogClient, runId: string): Promise<void>;
|
|
48
|
+
/** The turn failed. The message is recorded on the run before it is rethrown. */
|
|
49
|
+
export declare function markLocalRunFailed(store: GraphqlRunLogClient, runId: string, error: unknown, now?: Date): Promise<void>;
|
package/local-run.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A run executed by a host on the user's own machine.
|
|
4
|
+
*
|
|
5
|
+
* The cloud lane opens its run on the platform's side — the worker creates the
|
|
6
|
+
* row, then hands the Job a callback lane to append through. A local host has no
|
|
7
|
+
* worker in front of it: it authenticates as the user against the tenant API and
|
|
8
|
+
* so opens its own thread and run, which is why `placement` is stated here
|
|
9
|
+
* rather than defaulted, and why nothing in this file passes an identity. The
|
|
10
|
+
* claims of the request's transaction are the run's owner, and RLS on
|
|
11
|
+
* `agent_run`/`agent_event` is the same visibility rule a cloud run obeys.
|
|
12
|
+
*
|
|
13
|
+
* Kept in this package rather than in the desktop app so the host that opens a
|
|
14
|
+
* local run and the reader the web UI draws it with are the same code.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.openLocalRun = openLocalRun;
|
|
18
|
+
exports.adoptLocalRun = adoptLocalRun;
|
|
19
|
+
exports.markLocalRunRunning = markLocalRunRunning;
|
|
20
|
+
exports.markLocalRunIdle = markLocalRunIdle;
|
|
21
|
+
exports.markLocalRunFailed = markLocalRunFailed;
|
|
22
|
+
/**
|
|
23
|
+
* Open a durable local run: a thread if the host does not already have one, then
|
|
24
|
+
* a `pending` run marked `placement: 'local'`.
|
|
25
|
+
*/
|
|
26
|
+
async function openLocalRun(options) {
|
|
27
|
+
const { store } = options;
|
|
28
|
+
const threadId = options.threadId ??
|
|
29
|
+
(await store.createThread({ title: options.title, mode: 'agent' })).id;
|
|
30
|
+
const run = await store.createRun({
|
|
31
|
+
threadId,
|
|
32
|
+
placement: 'local',
|
|
33
|
+
status: 'pending',
|
|
34
|
+
...(options.repoUrl === undefined ? {} : { repoUrl: options.repoUrl }),
|
|
35
|
+
...(options.branch === undefined ? {} : { branch: options.branch }),
|
|
36
|
+
});
|
|
37
|
+
if (run.placement !== 'local') {
|
|
38
|
+
throw new Error(`run ${run.id} was opened as placement '${run.placement}', not 'local'; the tenant refused the placement this host declared`);
|
|
39
|
+
}
|
|
40
|
+
return { runId: run.id, threadId };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Adopt a run this host opened earlier — the resume path, and the check that a
|
|
44
|
+
* cached run id still belongs to the signed-in user. Throws when the run is gone
|
|
45
|
+
* or invisible, so a stale cache cannot silently start a second history.
|
|
46
|
+
*/
|
|
47
|
+
async function adoptLocalRun(store, runId) {
|
|
48
|
+
const run = await store.getRun(runId);
|
|
49
|
+
if (run.placement !== 'local') {
|
|
50
|
+
throw new Error(`run ${runId} has placement '${run.placement}'; a local host must not take over a cloud run`);
|
|
51
|
+
}
|
|
52
|
+
return run;
|
|
53
|
+
}
|
|
54
|
+
/** The host has picked the run up and is about to drive the model. */
|
|
55
|
+
async function markLocalRunRunning(store, runId, now = new Date()) {
|
|
56
|
+
await store.updateRun(runId, {
|
|
57
|
+
status: 'running',
|
|
58
|
+
startedAt: now.toISOString(),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The turn finished and the host is waiting for the user. `idle` rather than
|
|
63
|
+
* `succeeded` for the same reason a cloud chat run goes idle: the session can be
|
|
64
|
+
* prompted again, and only a closed session is terminal.
|
|
65
|
+
*/
|
|
66
|
+
async function markLocalRunIdle(store, runId) {
|
|
67
|
+
await store.updateRun(runId, { status: 'idle' });
|
|
68
|
+
}
|
|
69
|
+
/** The turn failed. The message is recorded on the run before it is rethrown. */
|
|
70
|
+
async function markLocalRunFailed(store, runId, error, now = new Date()) {
|
|
71
|
+
await store.updateRun(runId, {
|
|
72
|
+
status: 'failed',
|
|
73
|
+
error: error instanceof Error ? error.message : String(error),
|
|
74
|
+
finishedAt: now.toISOString(),
|
|
75
|
+
});
|
|
76
|
+
}
|
package/names.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GraphQL names a module's run surface is exposed under.
|
|
3
|
+
*
|
|
4
|
+
* The tables are provisioned per agent module (`agent_module.has_runs`), and a
|
|
5
|
+
* module carries its own table names — `agent_run`/`agent_event` at app scope,
|
|
6
|
+
* `org_agent_run`/`org_agent_event` at org scope — so a reader cannot hardcode
|
|
7
|
+
* the fields it queries. Everything here is derived from the two physical names
|
|
8
|
+
* the module loader already reports, using the same inflection the generated
|
|
9
|
+
* SDK targets use: `agent_event` → node `agentEvent`, connection `agentEvents`,
|
|
10
|
+
* mutation `createAgentEvent`, order-by `SEQ_ASC`.
|
|
11
|
+
*
|
|
12
|
+
* Derived rather than injected because the alternative is a table of names per
|
|
13
|
+
* tenant, which drifts the moment a module is provisioned under a new scope.
|
|
14
|
+
*/
|
|
15
|
+
export interface RunLogNames {
|
|
16
|
+
/** Physical table name the rest of the names derive from. */
|
|
17
|
+
runTable: string;
|
|
18
|
+
eventTable: string;
|
|
19
|
+
/**
|
|
20
|
+
* The thread a run hangs from. `agent_run.thread_id` is required, so a host
|
|
21
|
+
* that opens its own run — a local one — needs the thread's names too; a host
|
|
22
|
+
* that only reads runs never touches them.
|
|
23
|
+
*/
|
|
24
|
+
threadTable: string;
|
|
25
|
+
/** Single-node field, e.g. `agentRun`. */
|
|
26
|
+
run: string;
|
|
27
|
+
event: string;
|
|
28
|
+
thread: string;
|
|
29
|
+
/** Connection field, e.g. `agentRuns`. */
|
|
30
|
+
runs: string;
|
|
31
|
+
events: string;
|
|
32
|
+
threads: string;
|
|
33
|
+
/** Type name, e.g. `AgentRun` — filters and inputs are named after it. */
|
|
34
|
+
runType: string;
|
|
35
|
+
eventType: string;
|
|
36
|
+
threadType: string;
|
|
37
|
+
createEvent: string;
|
|
38
|
+
createRun: string;
|
|
39
|
+
createThread: string;
|
|
40
|
+
updateRun: string;
|
|
41
|
+
/** Patch field on the update input, e.g. `agentRunPatch`. */
|
|
42
|
+
runPatch: string;
|
|
43
|
+
}
|
|
44
|
+
export declare const DEFAULT_RUN_TABLE = "agent_run";
|
|
45
|
+
export declare const DEFAULT_EVENT_TABLE = "agent_event";
|
|
46
|
+
export declare const DEFAULT_THREAD_TABLE = "agent_thread";
|
|
47
|
+
export declare function runLogNames(runTable?: string, eventTable?: string, threadTable?: string): RunLogNames;
|