@knpkv/relay 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -3
- package/dist/harness.d.ts +8 -4
- package/dist/harness.d.ts.map +1 -1
- package/dist/index.js +150 -31
- package/dist/index.js.map +4 -4
- package/dist/messages.d.ts +44 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/model.d.ts +19 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/wire.d.ts +214 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +145 -0
- package/dist/wire.js.map +7 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -50,7 +50,10 @@ const RelayLive = Layer.unwrap(
|
|
|
50
50
|
the run that answers it. `backend` switches the session from its next turn on (`RelayBackendNotConfigured`
|
|
51
51
|
otherwise). `context` is what the person attached, as the product renders it (`{ label, body }`): it is
|
|
52
52
|
placed just before the message for the model, and never appears in the transcript or the Snapshot.
|
|
53
|
-
- `events(ref)`: a `Snapshot` first, with each message's `id
|
|
53
|
+
- `events(ref)`: a `Snapshot` first, with each message's `id`, the `runIds` of a run in flight and the
|
|
54
|
+
requestIds still `queued`. Then:
|
|
55
|
+
- `MessageQueued { requestId }`, then `MessagePlaced { id, requestId, text }` when a run takes it, or
|
|
56
|
+
`MessageWithdrawn { requestId }` when it is cancelled first.
|
|
54
57
|
- `RunStarted { runIds }` and `TextDelta`.
|
|
55
58
|
- `ToolStarted` and `ToolFinished`, each with a server-built, display-safe `summary`. A finished call carries
|
|
56
59
|
its citations, and a write whose product projects one carries a `receipt { summary, providerId, link? }`
|
|
@@ -67,14 +70,31 @@ const RelayLive = Layer.unwrap(
|
|
|
67
70
|
`NotInstalled`, `SignedOut`, `Misconfigured` or `NoCapability` and a one-line fix. Observed, never persisted.
|
|
68
71
|
`SignedOut` comes from an `AuthenticationError` turn failure.
|
|
69
72
|
|
|
73
|
+
## In the browser: `@knpkv/relay/wire`
|
|
74
|
+
|
|
75
|
+
The contract a product's `/…/relay` routes speak, as a separate browser bundle that imports only
|
|
76
|
+
`effect` and `@knpkv/capability`. It holds the harness's event and session schemas, plus:
|
|
77
|
+
|
|
78
|
+
- `RelayStreamFrame`: one `data:` frame of `GET events`, a `RelayEvent`, `Unauthorized` or `StreamFailed`;
|
|
79
|
+
- `RelayMessageRequest`, `RelayCancelRequest` and `RelayDecisionRequest`, the write bodies, and `RelayMessageAccepted`;
|
|
80
|
+
- the typed refusals: `RelayUnavailableError` (503, with the fix), `RelayConflictError` (409, with the
|
|
81
|
+
`RelayConflictState` found) and `RelayBadRequestError` (400).
|
|
82
|
+
|
|
83
|
+
A product narrows `ref` to its own objects and may add a typed `context` to a message. The build fails if
|
|
84
|
+
the bundle reaches anything else, and `pnpm test:pack` checks the packed `dist/wire.js` again.
|
|
85
|
+
`@knpkv/relay-product/client` reads a conversation through it.
|
|
86
|
+
|
|
70
87
|
## Security boundaries
|
|
71
88
|
|
|
72
89
|
- The session store holds conversation content. Its directory is created `0700` and the database `0600`;
|
|
73
90
|
a store directory, database or SQLite sidecar (`-wal`, `-shm`) that is a symbolic link, even a dangling one, fails with `RelayStoreLinked` before anything is
|
|
74
91
|
re-permissioned or written;
|
|
75
92
|
it is never logged, never sent to telemetry, and never served on unauthenticated routes.
|
|
76
|
-
- One process owns a store:
|
|
77
|
-
with `RelayStoreLocked
|
|
93
|
+
- One process owns a store: it holds an exclusive SQLite lock on `<store>.lock` (owner-only), so a second
|
|
94
|
+
owner fails with `RelayStoreLocked`. Closing the harness frees the store at once, so the same process can
|
|
95
|
+
open it again; the OS releases the lock if the owner dies. The lock is the runtime's own SQLite
|
|
96
|
+
(`node:sqlite` on Node, `bun:sqlite` on Bun), loaded when a store opens and used only through `exec`,
|
|
97
|
+
because libsql keeps a closed connection, and its locks, until its statements are garbage-collected.
|
|
78
98
|
- Provider credentials stay with the user's CLI. Relay only spawns `claude`/`codex` with all of their tools
|
|
79
99
|
withheld.
|
|
80
100
|
|
package/dist/harness.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Crypto, Effect, FileSystem, Layer, Path, Schema, Stream } from "effect";
|
|
1
|
+
import { Context, Crypto, Effect, FileSystem, Layer, Path, Schema, Scope, Stream } from "effect";
|
|
2
2
|
import type { LanguageModel } from "effect/ai";
|
|
3
3
|
import { RelayBackendId } from "./model.js";
|
|
4
4
|
import type { BackendStatus, ObjectRef, RelayEvent, SessionInfo, SessionTool } from "./model.js";
|
|
@@ -120,9 +120,13 @@ export interface RelayHarnessService {
|
|
|
120
120
|
declare const RelayHarness_base: Context.ServiceClass<RelayHarness, "@knpkv/relay/RelayHarness", RelayHarnessService>;
|
|
121
121
|
export declare class RelayHarness extends RelayHarness_base {
|
|
122
122
|
}
|
|
123
|
-
/**
|
|
124
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Build the harness for one product. One attempt's resources (the store lock, the store, the harness) live in
|
|
125
|
+
* their own scope: an attempt that fails releases them at once, so a retry in the same scope can take the
|
|
126
|
+
* store; one that succeeds keeps them until the caller's scope closes.
|
|
127
|
+
*/
|
|
128
|
+
export declare const make: <Requirements>(options: RelayHarnessOptions<Requirements>) => Effect.Effect<RelayHarnessService, RelayStoreFailed | RelayStoreLinked | RelayStoreLocked, Crypto.Crypto | FileSystem.FileSystem | Path.Path | Scope.Scope | Exclude<Requirements, Scope.Scope>>;
|
|
125
129
|
/** Build the harness as a scoped layer: the store closes, and the lock is released, with the scope. */
|
|
126
|
-
export declare const layer: <Requirements>(options: RelayHarnessOptions<Requirements>) => Layer.Layer<RelayHarness, RelayStoreFailed | RelayStoreLinked | RelayStoreLocked, Crypto.Crypto | FileSystem.FileSystem | Path.Path | Exclude<Requirements,
|
|
130
|
+
export declare const layer: <Requirements>(options: RelayHarnessOptions<Requirements>) => Layer.Layer<RelayHarness, RelayStoreFailed | RelayStoreLinked | RelayStoreLocked, Crypto.Crypto | FileSystem.FileSystem | Path.Path | Exclude<Exclude<Requirements, Scope.Scope>, Scope.Scope>>;
|
|
127
131
|
export {};
|
|
128
132
|
//# sourceMappingURL=harness.d.ts.map
|
package/dist/harness.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -4757,10 +4757,10 @@ async function finalResult(runtime, call, result, reported, context) {
|
|
|
4757
4757
|
harness.push(truncated(bounded, reported.limits.retain));
|
|
4758
4758
|
return { ...final, content: bounded.content, diagnostics: [...final.diagnostics ?? [], ...harness] };
|
|
4759
4759
|
}
|
|
4760
|
-
async function settle(runtime, call, ending,
|
|
4760
|
+
async function settle(runtime, call, ending, build2, context) {
|
|
4761
4761
|
await runtime.commit(async (tx) => {
|
|
4762
4762
|
const slot = toolSlot(await tx.doc(LiveDoc, runtime.conversationId), runtime.taskId);
|
|
4763
|
-
const result =
|
|
4763
|
+
const result = build2(slot);
|
|
4764
4764
|
const entry = await appendToolResult(tx, runtime.conversationId, call, result, runtime.now());
|
|
4765
4765
|
if (slot !== void 0)
|
|
4766
4766
|
finishSlot(slot, entry.id);
|
|
@@ -9257,11 +9257,11 @@ var HarnessImpl = class extends SessionImpl {
|
|
|
9257
9257
|
}
|
|
9258
9258
|
/** Build a conversation's environment from its current `cwd`; `undefined` without an `env` option. */
|
|
9259
9259
|
async buildEnv(id, context) {
|
|
9260
|
-
const
|
|
9261
|
-
if (
|
|
9260
|
+
const build2 = this.#options.env;
|
|
9261
|
+
if (build2 === void 0)
|
|
9262
9262
|
return void 0;
|
|
9263
9263
|
const cwd = (await this.snapshot(AgentDoc, id, context))?.cwd;
|
|
9264
|
-
return
|
|
9264
|
+
return build2({ conversationId: id, ...cwd === void 0 ? {} : { cwd }, read: this }, context);
|
|
9265
9265
|
}
|
|
9266
9266
|
/** Reconcile surviving `running` tasks to `pending`; part of open. */
|
|
9267
9267
|
openTasks(context) {
|
|
@@ -10117,8 +10117,9 @@ var SqliteStorage = class _SqliteStorage {
|
|
|
10117
10117
|
// dist/harness.js
|
|
10118
10118
|
import * as Capability2 from "@knpkv/capability";
|
|
10119
10119
|
import { createClient } from "@libsql/client";
|
|
10120
|
-
import { Clock, Context, Crypto, Deferred, Effect as Effect3, Exit, FileSystem, Layer, Path, Predicate as
|
|
10120
|
+
import { Clock, Context, Crypto, Deferred, Effect as Effect3, Exit, FileSystem, Layer, Path, Predicate as Predicate4, PubSub, Queue, Ref, Schema as Schema5, Scope, Semaphore, Stream } from "effect";
|
|
10121
10121
|
import { AiError } from "effect/ai";
|
|
10122
|
+
import { createRequire } from "node:module";
|
|
10122
10123
|
|
|
10123
10124
|
// dist/libsqlDatabase.js
|
|
10124
10125
|
import { Predicate } from "effect";
|
|
@@ -10174,6 +10175,48 @@ var libsqlDatabase = (client) => {
|
|
|
10174
10175
|
};
|
|
10175
10176
|
};
|
|
10176
10177
|
|
|
10178
|
+
// dist/messages.js
|
|
10179
|
+
import { Predicate as Predicate2 } from "effect";
|
|
10180
|
+
var entryText = (messages) => (messages ?? []).flatMap((message) => message.role === "user" || message.role === "assistant" ? Predicate2.isString(message.content) ? [message.content] : message.content.flatMap((block) => block.type === "text" && block.text !== void 0 ? [block.text] : []) : []).join("\n");
|
|
10181
|
+
var makeMessageTracker = () => {
|
|
10182
|
+
const texts = /* @__PURE__ */ new Map();
|
|
10183
|
+
const requests = /* @__PURE__ */ new Map();
|
|
10184
|
+
const announced = /* @__PURE__ */ new Set();
|
|
10185
|
+
const placed = (entry) => {
|
|
10186
|
+
const text = texts.get(entry);
|
|
10187
|
+
const requestId = requests.get(entry);
|
|
10188
|
+
if (text === void 0 || requestId === void 0 || announced.has(entry))
|
|
10189
|
+
return [];
|
|
10190
|
+
announced.add(entry);
|
|
10191
|
+
texts.delete(entry);
|
|
10192
|
+
requests.delete(entry);
|
|
10193
|
+
return [{ _tag: "MessagePlaced", id: entry, requestId, text }];
|
|
10194
|
+
};
|
|
10195
|
+
return {
|
|
10196
|
+
/** A committed entry; only a person's message can place one. */
|
|
10197
|
+
entry: (entry) => {
|
|
10198
|
+
if (entry.kind !== "pi.user" || announced.has(String(entry.id)))
|
|
10199
|
+
return [];
|
|
10200
|
+
texts.set(String(entry.id), entryText(entry.model));
|
|
10201
|
+
return placed(String(entry.id));
|
|
10202
|
+
},
|
|
10203
|
+
/** A submission record; Relay submits every input with the dock's requestId, its id is the fallback. */
|
|
10204
|
+
record: (record) => {
|
|
10205
|
+
if (record.type !== "input")
|
|
10206
|
+
return [];
|
|
10207
|
+
const requestId = record.requestId ?? String(record.id);
|
|
10208
|
+
if (record.entry === void 0) {
|
|
10209
|
+
return [{ _tag: record.status === "queued" ? "MessageQueued" : "MessageWithdrawn", requestId }];
|
|
10210
|
+
}
|
|
10211
|
+
const entry = String(record.entry);
|
|
10212
|
+
if (announced.has(entry))
|
|
10213
|
+
return [];
|
|
10214
|
+
requests.set(entry, requestId);
|
|
10215
|
+
return placed(entry);
|
|
10216
|
+
}
|
|
10217
|
+
};
|
|
10218
|
+
};
|
|
10219
|
+
|
|
10177
10220
|
// dist/model.js
|
|
10178
10221
|
import * as Capability from "@knpkv/capability";
|
|
10179
10222
|
import { Schema } from "effect";
|
|
@@ -10228,8 +10271,19 @@ var RelayEvent = Schema.TaggedUnion({
|
|
|
10228
10271
|
...eventFields,
|
|
10229
10272
|
messages: Schema.Array(Schema.Struct({ id: Name, role: Schema.Literals(["user", "relay"]), text: Schema.String })),
|
|
10230
10273
|
/** The run in flight, empty when idle. */
|
|
10231
|
-
runIds: RunIds
|
|
10274
|
+
runIds: RunIds,
|
|
10275
|
+
/** Messages accepted but not yet in the transcript, oldest first: they wait for the run in flight. */
|
|
10276
|
+
queued: RunIds
|
|
10232
10277
|
},
|
|
10278
|
+
/**
|
|
10279
|
+
* A message the server accepted that waits for the run in flight. It joins the transcript with its own
|
|
10280
|
+
* `MessagePlaced`, or leaves with `MessageWithdrawn`.
|
|
10281
|
+
*/
|
|
10282
|
+
MessageQueued: { ...eventFields, requestId: Name },
|
|
10283
|
+
/** A message the server placed in the transcript; `id` is its transcript id, as a later Snapshot names it. */
|
|
10284
|
+
MessagePlaced: { ...eventFields, id: Name, requestId: Name, text: Schema.String },
|
|
10285
|
+
/** A queued message the person withdrew before any run took it. */
|
|
10286
|
+
MessageWithdrawn: { ...eventFields, requestId: Name },
|
|
10233
10287
|
RunStarted: { ...eventFields, runIds: RunIds },
|
|
10234
10288
|
TextDelta: { ...eventFields, text: Schema.String },
|
|
10235
10289
|
/** `summary` is a server-built, display-safe line; `input` is the call's typed arguments. */
|
|
@@ -11309,11 +11363,11 @@ function createProvider(input) {
|
|
|
11309
11363
|
}
|
|
11310
11364
|
|
|
11311
11365
|
// dist/piProvider.js
|
|
11312
|
-
import { Predicate as
|
|
11366
|
+
import { Predicate as Predicate3, Schema as Schema2 } from "effect";
|
|
11313
11367
|
var API = "relay-cli-turn";
|
|
11314
11368
|
var decodeJson = Schema2.decodeUnknownOption(Schema2.Json);
|
|
11315
11369
|
var MODEL_ID = "default";
|
|
11316
|
-
var textOf = (content) =>
|
|
11370
|
+
var textOf = (content) => Predicate3.isString(content) ? content : content.flatMap((block) => block.type === "text" && block.text !== void 0 ? [block.text] : []).join("\n");
|
|
11317
11371
|
var turnRequestOf = (messages) => {
|
|
11318
11372
|
const instructions = [];
|
|
11319
11373
|
const tools = /* @__PURE__ */ new Map();
|
|
@@ -11618,6 +11672,27 @@ var RelayRunNotActive = class extends Schema5.TaggedError()("RelayRunNotActive",
|
|
|
11618
11672
|
runId: Schema5.String
|
|
11619
11673
|
}) {
|
|
11620
11674
|
};
|
|
11675
|
+
var runFailure = (reason) => {
|
|
11676
|
+
switch (reason) {
|
|
11677
|
+
case "model_error":
|
|
11678
|
+
return {
|
|
11679
|
+
cause: "The backend returned an error.",
|
|
11680
|
+
fix: "Check the backend in Relay setup (installed and signed in), then send the message again."
|
|
11681
|
+
};
|
|
11682
|
+
case "no_model":
|
|
11683
|
+
return {
|
|
11684
|
+
cause: "No backend is set up for Relay.",
|
|
11685
|
+
fix: "Choose a backend in Relay setup, then send the message again."
|
|
11686
|
+
};
|
|
11687
|
+
case "reset":
|
|
11688
|
+
return { cause: "The conversation was reset before Relay answered.", fix: "Send the message again." };
|
|
11689
|
+
default:
|
|
11690
|
+
return {
|
|
11691
|
+
cause: "The run stopped on an error.",
|
|
11692
|
+
fix: "Send the message again; if it fails again, check the backend in Relay setup."
|
|
11693
|
+
};
|
|
11694
|
+
}
|
|
11695
|
+
};
|
|
11621
11696
|
var RelayBackendNotConfigured = class extends Schema5.TaggedError()("RelayBackendNotConfigured", { backend: RelayBackendId }) {
|
|
11622
11697
|
};
|
|
11623
11698
|
var RelayBackendUnavailable = class extends Schema5.TaggedError()("RelayBackendUnavailable", { cause: BackendUnavailableCause, fix: Schema5.String }) {
|
|
@@ -11636,7 +11711,48 @@ var promise = (operation, run2) => Effect3.tryPromise({
|
|
|
11636
11711
|
try: run2,
|
|
11637
11712
|
catch: (cause) => new RelayStoreFailed({ operation, message: `${operation} failed: ${String(cause)}` })
|
|
11638
11713
|
});
|
|
11639
|
-
var notALink = (error) => error.reason._tag === "NotFound" || error.reason._tag !== "BadArgument" &&
|
|
11714
|
+
var notALink = (error) => error.reason._tag === "NotFound" || error.reason._tag !== "BadArgument" && Predicate4.hasProperty(error.reason.cause, "code") && error.reason.cause.code === "EINVAL";
|
|
11715
|
+
var lockDriver = Effect3.tryPromise({
|
|
11716
|
+
try: async () => {
|
|
11717
|
+
try {
|
|
11718
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
11719
|
+
return (path) => new DatabaseSync(path);
|
|
11720
|
+
} catch (nodeFailure) {
|
|
11721
|
+
try {
|
|
11722
|
+
const bun = createRequire(import.meta.url)("bun:sqlite");
|
|
11723
|
+
return (path) => new bun.Database(path);
|
|
11724
|
+
} catch (bunFailure) {
|
|
11725
|
+
throw new AggregateError([nodeFailure, bunFailure], `neither node:sqlite (${String(nodeFailure)}) nor bun:sqlite (${String(bunFailure)}) could load`, { cause: bunFailure });
|
|
11726
|
+
}
|
|
11727
|
+
}
|
|
11728
|
+
},
|
|
11729
|
+
catch: (cause) => new RelayStoreFailed({
|
|
11730
|
+
operation: "load SQLite for the Relay store lock",
|
|
11731
|
+
message: `load SQLite for the Relay store lock failed: ${String(cause)}`
|
|
11732
|
+
})
|
|
11733
|
+
});
|
|
11734
|
+
var isBusy = (cause) => Predicate4.hasProperty(cause, "errcode") && cause.errcode === 5 || Predicate4.hasProperty(cause, "errno") && cause.errno === 5 || Predicate4.hasProperty(cause, "code") && cause.code === "SQLITE_BUSY";
|
|
11735
|
+
var acquireStoreLock = (path) => Effect3.acquireRelease(Effect3.flatMap(lockDriver, (open) => Effect3.try({
|
|
11736
|
+
try: () => {
|
|
11737
|
+
const lock = open(path);
|
|
11738
|
+
try {
|
|
11739
|
+
lock.exec("PRAGMA locking_mode = EXCLUSIVE");
|
|
11740
|
+
lock.exec("BEGIN IMMEDIATE");
|
|
11741
|
+
lock.exec("COMMIT");
|
|
11742
|
+
return lock;
|
|
11743
|
+
} catch (failure) {
|
|
11744
|
+
lock.close();
|
|
11745
|
+
throw failure;
|
|
11746
|
+
}
|
|
11747
|
+
},
|
|
11748
|
+
catch: (cause) => isBusy(cause) ? new RelayStoreLocked({
|
|
11749
|
+
path,
|
|
11750
|
+
message: `Another process owns the Relay store at ${path.replace(/\.lock$/u, "")}. Stop it, then retry.`
|
|
11751
|
+
}) : new RelayStoreFailed({
|
|
11752
|
+
operation: "lock the Relay store",
|
|
11753
|
+
message: `lock the Relay store failed: ${String(cause)}`
|
|
11754
|
+
})
|
|
11755
|
+
})), (lock) => Effect3.sync(() => lock.close()));
|
|
11640
11756
|
var openStore = (path) => Effect3.acquireRelease(Effect3.gen(function* () {
|
|
11641
11757
|
const fs = yield* FileSystem.FileSystem;
|
|
11642
11758
|
const paths = yield* Path.Path;
|
|
@@ -11654,17 +11770,17 @@ var openStore = (path) => Effect3.acquireRelease(Effect3.gen(function* () {
|
|
|
11654
11770
|
}));
|
|
11655
11771
|
yield* refuseLink(directory, "store directory");
|
|
11656
11772
|
yield* fs.makeDirectory(directory, { recursive: true, mode: 448 }).pipe(ownerOnly("create the Relay store directory"));
|
|
11657
|
-
yield* Effect3.forEach([path, `${path}-wal`, `${path}-shm`], (file) => refuseLink(file, "store"), {
|
|
11773
|
+
yield* Effect3.forEach([path, `${path}-wal`, `${path}-shm`, `${path}.lock`], (file) => refuseLink(file, "store"), {
|
|
11658
11774
|
discard: true
|
|
11659
11775
|
});
|
|
11660
11776
|
yield* fs.chmod(directory, 448).pipe(ownerOnly("restrict the Relay store directory"));
|
|
11777
|
+
yield* acquireStoreLock(`${path}.lock`);
|
|
11778
|
+
yield* fs.chmod(`${path}.lock`, 384).pipe(ownerOnly("restrict the Relay store lock"));
|
|
11661
11779
|
const client = createClient({ url: `file:${path}` });
|
|
11662
11780
|
const database = libsqlDatabase(client);
|
|
11663
11781
|
yield* promise("open the Relay store", async () => {
|
|
11664
|
-
await database.exec("PRAGMA locking_mode = EXCLUSIVE");
|
|
11665
11782
|
await database.exec("PRAGMA journal_mode = WAL");
|
|
11666
11783
|
await database.exec("PRAGMA synchronous = NORMAL");
|
|
11667
|
-
await database.exec("BEGIN IMMEDIATE; COMMIT");
|
|
11668
11784
|
}).pipe(Effect3.andThen(fs.chmod(path, 384).pipe(ownerOnly("restrict the Relay store"))), Effect3.catchTag("RelayStoreFailed", (failure) => /SQLITE_BUSY|database is locked/u.test(failure.message) ? Effect3.fail(new RelayStoreLocked({
|
|
11669
11785
|
path,
|
|
11670
11786
|
message: `Another process owns the Relay store at ${path}. Stop it, then retry.`
|
|
@@ -11672,6 +11788,11 @@ var openStore = (path) => Effect3.acquireRelease(Effect3.gen(function* () {
|
|
|
11672
11788
|
return database;
|
|
11673
11789
|
}), (database) => Effect3.promise(() => database.close()));
|
|
11674
11790
|
var make = Effect3.fn("RelayHarness.make")(function* (options) {
|
|
11791
|
+
const owner = yield* Scope.Scope;
|
|
11792
|
+
const attempt = yield* Scope.fork(owner, "sequential");
|
|
11793
|
+
return yield* build(options).pipe(Scope.provide(attempt), Effect3.onExit((exit) => Exit.isFailure(exit) ? Scope.close(attempt, exit) : Effect3.void));
|
|
11794
|
+
});
|
|
11795
|
+
var build = Effect3.fn("RelayHarness.build")(function* (options) {
|
|
11675
11796
|
const services = yield* Effect3.context();
|
|
11676
11797
|
const runEffect = (effect, signal) => Effect3.runPromiseExitWith(services)(effect, signal === void 0 ? void 0 : { signal });
|
|
11677
11798
|
const runPromise = Effect3.runPromiseWith(services);
|
|
@@ -11861,7 +11982,6 @@ ${item.body}`,
|
|
|
11861
11982
|
});
|
|
11862
11983
|
});
|
|
11863
11984
|
var layer = (options) => Layer.effect(RelayHarness, make(options));
|
|
11864
|
-
var entryText = (messages) => (messages ?? []).flatMap((message) => message.role === "user" || message.role === "assistant" ? Predicate3.isString(message.content) ? [message.content] : message.content.flatMap((block) => block.type === "text" ? [block.text] : []) : []).join("\n");
|
|
11865
11985
|
var decodeDetails = Schema5.decodeUnknownOption(Schema5.Struct({ cites: Schema5.Array(Capability2.ObjectRef), receipt: Schema5.optionalKey(WriteReceipt) }));
|
|
11866
11986
|
var sessionEvents = (harness, conversation, confirmations, pendingFor, display) => {
|
|
11867
11987
|
const summarize = (capability, args) => display(capability)?.summarize(args) ?? capability;
|
|
@@ -11905,7 +12025,9 @@ var sessionEvents = (harness, conversation, confirmations, pendingFor, display)
|
|
|
11905
12025
|
}]);
|
|
11906
12026
|
return [...partial, ...running];
|
|
11907
12027
|
};
|
|
11908
|
-
const
|
|
12028
|
+
const messages = makeMessageTracker();
|
|
12029
|
+
const fromMessages = (events) => events.map((event) => ({ ...event, session }));
|
|
12030
|
+
const toRelay = (event, runIds, queued2 = []) => {
|
|
11909
12031
|
switch (event.type) {
|
|
11910
12032
|
case "snapshot":
|
|
11911
12033
|
return [
|
|
@@ -11913,6 +12035,7 @@ var sessionEvents = (harness, conversation, confirmations, pendingFor, display)
|
|
|
11913
12035
|
_tag: "Snapshot",
|
|
11914
12036
|
session,
|
|
11915
12037
|
runIds,
|
|
12038
|
+
queued: queued2,
|
|
11916
12039
|
messages: event.entries.flatMap((entry) => entry.kind === "pi.user" ? [{ id: String(entry.id), role: "user", text: entryText(entry.model) }] : entry.kind === "pi.assistant" ? [{ id: String(entry.id), role: "relay", text: entryText(entry.model) }] : [])
|
|
11917
12040
|
},
|
|
11918
12041
|
...inFlight(event),
|
|
@@ -11921,6 +12044,11 @@ var sessionEvents = (harness, conversation, confirmations, pendingFor, display)
|
|
|
11921
12044
|
case "message_start":
|
|
11922
12045
|
sent.clear();
|
|
11923
12046
|
return event.message.role === "assistant" ? event.message.content.flatMap((block, index) => block.type === "text" ? textFrom(index, block.text) : []) : [];
|
|
12047
|
+
case "message_end":
|
|
12048
|
+
case "entry_appended":
|
|
12049
|
+
return fromMessages(messages.entry(event.entry));
|
|
12050
|
+
case "submission":
|
|
12051
|
+
return fromMessages(messages.record(event.record));
|
|
11924
12052
|
case "message_update":
|
|
11925
12053
|
return event.changes.flatMap((change) => {
|
|
11926
12054
|
if (change.type === "text_delta") {
|
|
@@ -11964,13 +12092,7 @@ var sessionEvents = (harness, conversation, confirmations, pendingFor, display)
|
|
|
11964
12092
|
}];
|
|
11965
12093
|
}
|
|
11966
12094
|
case "task_failed":
|
|
11967
|
-
return [{
|
|
11968
|
-
_tag: "RunFailed",
|
|
11969
|
-
session,
|
|
11970
|
-
runIds,
|
|
11971
|
-
cause: "The run stopped on an error",
|
|
11972
|
-
fix: "Retry the message; if it fails again, check the backend in setup."
|
|
11973
|
-
}];
|
|
12095
|
+
return [{ _tag: "RunFailed", session, runIds, ...runFailure("task_failed") }];
|
|
11974
12096
|
default:
|
|
11975
12097
|
return [];
|
|
11976
12098
|
}
|
|
@@ -11979,6 +12101,10 @@ var sessionEvents = (harness, conversation, confirmations, pendingFor, display)
|
|
|
11979
12101
|
const runIdsOf = (inputs, read2) => inputs.map((id, index) => read2[index]?.requestId ?? String(id));
|
|
11980
12102
|
let active = [];
|
|
11981
12103
|
const track2 = (inputs) => (inputs.length === 0 ? Effect3.succeed([]) : promise("read the run in flight", async () => runIdsOf(inputs, await records(inputs)))).pipe(Effect3.tap((runIds) => Effect3.sync(() => active = runIds)));
|
|
12104
|
+
const queuedOf = (inbox) => {
|
|
12105
|
+
const inputs = inbox.map(({ id }) => id);
|
|
12106
|
+
return inputs.length === 0 ? Effect3.succeed([]) : promise("read the queued messages", async () => runIdsOf(inputs, await records(inputs)));
|
|
12107
|
+
};
|
|
11982
12108
|
const runEnded = (inputs) => promise("read how the run ended", async () => {
|
|
11983
12109
|
const read2 = await records(inputs);
|
|
11984
12110
|
const runIds = runIdsOf(inputs, read2);
|
|
@@ -11986,18 +12112,11 @@ var sessionEvents = (harness, conversation, confirmations, pendingFor, display)
|
|
|
11986
12112
|
if (unanswered === void 0 || unanswered.status !== "unanswered") {
|
|
11987
12113
|
return { _tag: "RunFinished", session, runIds };
|
|
11988
12114
|
}
|
|
11989
|
-
return /abort/iu.test(unanswered.reason) ? { _tag: "Cancelled", session, runIds } : {
|
|
11990
|
-
_tag: "RunFailed",
|
|
11991
|
-
session,
|
|
11992
|
-
runIds,
|
|
11993
|
-
// `reason` is Pi's code (`model_error`); `detail` carries the backend's own message when there is one.
|
|
11994
|
-
cause: Predicate3.isString(unanswered.detail) ? unanswered.detail : unanswered.reason,
|
|
11995
|
-
fix: "Check the backend in Relay setup (installed and signed in), then send the message again."
|
|
11996
|
-
};
|
|
12115
|
+
return /abort/iu.test(unanswered.reason) ? { _tag: "Cancelled", session, runIds } : { _tag: "RunFailed", session, runIds, ...runFailure(unanswered.reason) };
|
|
11997
12116
|
});
|
|
11998
12117
|
const gate = Stream.fromPubSub(confirmations).pipe(Stream.filter(({ conversationId }) => conversationId === session), Stream.map(({ event }) => event));
|
|
11999
12118
|
let seq = 0;
|
|
12000
|
-
const relayed = Stream.flatMap(fromPi, (event) => event.type === "run_end" ? Stream.fromEffect(runEnded(event.inputs).pipe(Effect3.tap(() => Effect3.sync(() => active = [])))) : event.type === "run_start" ? Stream.fromEffect(track2(event.inputs)).pipe(Stream.map((runIds) => ({ _tag: "RunStarted", session, runIds }))) : event.type === "snapshot" ? Stream.fromEffect(track2(event.run?.inputs ?? [])).pipe(Stream.flatMap((runIds) => Stream.fromIterable(toRelay(event, runIds)))) : Stream.fromIterable(toRelay(event, active)));
|
|
12119
|
+
const relayed = Stream.flatMap(fromPi, (event) => event.type === "run_end" ? Stream.fromEffect(runEnded(event.inputs).pipe(Effect3.tap(() => Effect3.sync(() => active = [])))) : event.type === "run_start" ? Stream.fromEffect(track2(event.inputs)).pipe(Stream.map((runIds) => ({ _tag: "RunStarted", session, runIds }))) : event.type === "snapshot" ? Stream.fromEffect(Effect3.all([track2(event.run?.inputs ?? []), queuedOf(event.inbox)])).pipe(Stream.flatMap(([runIds, queued2]) => Stream.fromIterable(toRelay(event, runIds, queued2)))) : Stream.fromIterable(toRelay(event, active)));
|
|
12001
12120
|
return Stream.merge(relayed, gate).pipe(Stream.map((event) => ({ ...event, seq: seq++ })));
|
|
12002
12121
|
};
|
|
12003
12122
|
|