@usecontextlayer/pi-durable-assistant-ui 1.0.0 → 2.0.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/README.md +37 -52
- package/dist/attachments.d.ts +2 -2
- package/dist/attachments.d.ts.map +1 -1
- package/dist/attachments.js +1 -1
- package/dist/client.d.ts +42 -13
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +112 -41
- package/dist/conversation.d.ts +44 -0
- package/dist/conversation.d.ts.map +1 -0
- package/dist/conversation.js +149 -0
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/metadata.d.ts +1 -2
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +0 -1
- package/dist/projection.d.ts +16 -2
- package/dist/projection.d.ts.map +1 -1
- package/dist/projection.js +41 -17
- package/dist/thread-list.d.ts +6 -0
- package/dist/thread-list.d.ts.map +1 -0
- package/dist/thread-list.js +39 -0
- package/dist/use-pi-durable-runtime.d.ts +22 -0
- package/dist/use-pi-durable-runtime.d.ts.map +1 -0
- package/dist/use-pi-durable-runtime.js +95 -0
- package/package.json +7 -6
- package/dist/store.d.ts +0 -57
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -261
- package/dist/use-pi-runtime.d.ts +0 -17
- package/dist/use-pi-runtime.d.ts.map +0 -1
- package/dist/use-pi-runtime.js +0 -49
package/README.md
CHANGED
|
@@ -1,83 +1,68 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
1
|
# Pi Durable for Assistant UI
|
|
4
2
|
|
|
5
|
-
Render
|
|
6
|
-
|
|
7
|
-
</div>
|
|
8
|
-
|
|
9
|
-
```tsx
|
|
10
|
-
import type { ReactNode } from "react";
|
|
11
|
-
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
12
|
-
import { createHttpPiClient, usePiRuntime } from "@usecontextlayer/pi-durable-assistant-ui";
|
|
13
|
-
const client = createHttpPiClient({ baseUrl: "/api" });
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
export function PiChat({ children }: { children: ReactNode }) {
|
|
18
|
-
const { runtime } = usePiRuntime({ client });
|
|
19
|
-
return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
|
|
20
|
-
}
|
|
21
|
-
```
|
|
3
|
+
Render native Pi Durable conversations through Assistant UI. Pi owns durable history, execution, submissions and configuration; Assistant UI owns thread navigation, drafts, initialization and directory pagination.
|
|
22
4
|
|
|
23
|
-
|
|
24
|
-
- **Keep Pi as the source of truth.** One thread maps to one native conversation. The browser stores no second durable transcript and runs no model.
|
|
25
|
-
- **Reconnect to ongoing work.** Streaming, earlier history, and reconnect gaps use native Pi views and `ConversationTranscript`. Closing a subscription does not cancel execution.
|
|
26
|
-
- **Choose your host.** Implement `PiClient` or use the included HTTP/SSE client with the demo's protocol. The adapter has no dependency on the demo's Node/SQLite host.
|
|
5
|
+
## Build and consume
|
|
27
6
|
|
|
28
|
-
|
|
7
|
+
From the repository root:
|
|
29
8
|
|
|
30
9
|
```sh
|
|
31
|
-
pnpm
|
|
10
|
+
pnpm install --frozen-lockfile
|
|
11
|
+
pnpm exec turbo run build --filter=@usecontextlayer/pi-durable-assistant-ui
|
|
12
|
+
pnpm --dir packages/adapter pack
|
|
32
13
|
```
|
|
33
14
|
|
|
34
|
-
|
|
15
|
+
Install the resulting tarball into your application, together with React 19, `@assistant-ui/react` compatible with `^0.15.32`, and Pi Durable compatible with `^1.5.0`. Pi Durable is a peer because native branded identifiers cross the host boundary. The checkout pins its verification dependencies in pnpm-lock.yaml. The adapter builds as ESM with TypeScript declarations and does not depend on the demo's Node/SQLite host.
|
|
35
16
|
|
|
36
|
-
|
|
17
|
+
```tsx
|
|
18
|
+
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
19
|
+
import {
|
|
20
|
+
createHttpPiDurableClient,
|
|
21
|
+
usePiDurableRuntime,
|
|
22
|
+
} from "@usecontextlayer/pi-durable-assistant-ui";
|
|
37
23
|
|
|
38
|
-
|
|
24
|
+
const client = createHttpPiDurableClient({ baseUrl: "/api" });
|
|
39
25
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
export function PiChat({ children }: { children: React.ReactNode }) {
|
|
27
|
+
const runtime = usePiDurableRuntime({ client });
|
|
28
|
+
return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
|
|
29
|
+
}
|
|
43
30
|
```
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## Runtime and actions
|
|
32
|
+
Keep the client stable. `usePiDurableRuntime` returns the standard Assistant UI runtime, so thread selection and directory actions use Assistant UI's APIs and primitives. Pass a controlled native `threadId` and `onThreadIdChange` to connect navigation to a URL. New threads are framework drafts until their first send initializes a native conversation. Assistant UI's temporary local identifiers remain internal; the canonical remote ID is the string representation of the native Pi conversation ID. Forks select their own native conversation as another thread.
|
|
48
33
|
|
|
49
|
-
|
|
34
|
+
The remote runtime retains visited thread runtimes until its lifecycle releases them. Each initialized thread owns a `PiDurableConversation`, its native `ConversationTranscript`, conversation observation and optional metadata observation. Closing an observation never cancels agent execution. Directory pages load on demand; the adapter reads metadata only for that page and does not rescan the entire directory after every answer.
|
|
50
35
|
|
|
51
|
-
|
|
36
|
+
## Native state and actions
|
|
52
37
|
|
|
53
|
-
|
|
38
|
+
Inside `AssistantRuntimeProvider`, `usePiDurable()` reads the current thread's `{ conversation, state }` extras. `conversation` is undefined for a draft; otherwise its ID and all its actions are bound to one native conversation. `state` contains the merged native view, metadata, transport connection/loading state and an error when one is reported. Native controls can use submission, continuation, steering, withdrawal, cancellation, configuration, forks, reset, compaction and earlier-history operations where the host supplies them.
|
|
54
39
|
|
|
55
|
-
|
|
40
|
+
For direct non-React observation, construct `new PiDurableConversation(client, id, pageSize)`, subscribe with `subscribe`/`getSnapshot`, then call `start` and `stop`. It has no selection method or global directory. Edit and regeneration return a native fork and target both the context write and continuation explicitly at that fork; the React integration then asks Assistant UI to select it.
|
|
56
41
|
|
|
57
|
-
|
|
42
|
+
## Host contract and observation
|
|
58
43
|
|
|
59
|
-
|
|
44
|
+
Implement the structural `PiDurableClient` or use `createHttpPiDurableClient` with the demo protocol. Installing the adapter does not install server routes. Optional client methods describe host extensions; titles, feedback, suggestions, uploads and human input require the corresponding host implementation.
|
|
60
45
|
|
|
61
|
-
|
|
46
|
+
`watch` returns Pi/Chord's standard asynchronous watch handle: current immutable `value`, `start`, `stop` and terminal `closed` outcome. HTTP carries an initial view, native Chord operations, recoverable source failures and terminal outcomes over one shared SSE connection per client. Logical subscriptions are tagged independently so retained thread runtimes do not exhaust browser HTTP connection limits. It reconstructs the same watch contract in the browser using Chord. Transport reconnection status remains separate from source termination. Native acquisition failures and terminal outcomes close the source rather than retrying indefinitely. Resume windows come from the thread's native transcript.
|
|
62
47
|
|
|
63
|
-
`
|
|
48
|
+
`watchMetadata`, when supplied, observes the host's native metadata document. The demo uses a conversation document for title, feedback keyed by native entry ID and static suggestions, with checkpoints on updates. The native creation hook initializes the document for each conversation, including forks. Native conversation views do not include arbitrary custom documents. Metadata changes update mounted conversations live; an observed title change refreshes Assistant UI's directory through its reload API. Newly created conversations from another client appear on explicit directory refresh. The optional batch metadata operation accepts only the IDs in the requested directory page.
|
|
64
49
|
|
|
65
|
-
|
|
50
|
+
`SerializedAgentChange` carries JSON tool, extension and model-tool names; the host resolves those names into registered native definitions. Omitted fields remain unchanged and null clears a field. Host-specific registry and execution-environment choices remain outside the adapter.
|
|
66
51
|
|
|
67
|
-
|
|
52
|
+
## Message semantics
|
|
68
53
|
|
|
69
|
-
|
|
54
|
+
The projection uses native entry IDs, joins tool results by native call ID and marks transient live messages optimistic so Assistant UI can evict them when committed entries arrive. Tool output is distinct from its UI-only artifact; native image output remains available as model content. The exported `PiDurableHumanInputDetails` artifact contract (native task ID, prompt, pending/answered status and answered response) becomes a text tool-approval request, and `onRespondToToolApproval` calls the host response operation for the native task. A response does not fabricate a browser-side tool result.
|
|
70
55
|
|
|
71
|
-
|
|
56
|
+
Reset and compaction notices are named `pi-context` data parts. Consumers register their own data renderer; the demo supplies one. Native entry guards classify those notices. Committed-history projection is memoized separately from live execution so unchanged message objects survive streaming updates. The pure `projectConversation` export remains available for non-React consumers.
|
|
72
57
|
|
|
73
|
-
|
|
58
|
+
## Queue and unsupported operations
|
|
74
59
|
|
|
75
|
-
The
|
|
60
|
+
The standard composer can send during a run through a queue adapter backed by Pi's native inbox. Assistant UI's normal busy send steers; `aui.composer().send({ steer: false })` enqueues a follow-up. Queue items retain native submission IDs, and removing one withdraws that submission. Pi owns processing and cancellation behavior; the adapter does not drain a second queue.
|
|
76
61
|
|
|
77
|
-
|
|
62
|
+
Pi Durable 1.5.0 has no native queue move/edit, permanent conversation deletion, archive or unarchive operation. Required delete, archive and queue move/edit methods reject explicitly; optional unarchive is omitted so Assistant UI reports that capability as unavailable. The demo exposes none of those controls. Archive is not simulated with a custom document. Withdrawal followed by another submission remains two distinct native operations with a new ID. Unsupported operations never report success.
|
|
78
63
|
|
|
79
|
-
##
|
|
64
|
+
## Attachments, feedback and voice
|
|
80
65
|
|
|
81
|
-
|
|
66
|
+
`createPiDurableAttachmentAdapter(client.uploadFile)` uses Assistant UI's attachment lifecycle and returns `CompleteAttachment`. The uploader supplies a name and native Pi input blocks; host storage paths, download URLs and content types remain host concerns. Removing a draft attachment does not delete an uploaded file from the host workspace.
|
|
82
67
|
|
|
83
|
-
|
|
68
|
+
Pass Assistant UI's `speech`, `dictation`, `attachments` and optional `feedback` adapters directly to the runtime hook. Without a feedback override, the native host feedback extension is used when available. Browser speech and dictation use Assistant UI's official adapters and support checks; actual microphone capture and audible playback remain device-dependent and unverified in this project.
|
package/dist/attachments.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AttachmentAdapter } from "@assistant-ui/react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { PiDurableUploadResult } from "./client.js";
|
|
3
3
|
/** Uploads at send time; the host decides which native Pi input represents the file. */
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function createPiDurableAttachmentAdapter(upload: (file: File, signal?: AbortSignal) => Promise<PiDurableUploadResult>): AttachmentAdapter;
|
|
5
5
|
//# sourceMappingURL=attachments.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAsB,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAsB,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,wFAAwF;AACxF,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,qBAAqB,CAAC,GAAG,iBAAiB,CAoBhJ"}
|
package/dist/attachments.js
CHANGED
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Op } from "@earendil-works/chord/delta";
|
|
2
|
+
import { type WatchHandle, type WatchEnd } from "@earendil-works/chord";
|
|
2
3
|
import type { ConversationCreateOptions, ConversationId, ConversationQuery, ConversationRecord, ConversationView, ConversationWindow, ConversationAbortOptions, ContextView, AgentState, CompactionResult, Cursor, EntryQuery, EntryRecord, EntryId, Page, SubmissionDraft, SubmissionId, SubmissionRecord, TaskId, UserInput, Harness, HarnessInspection } from "@earendil-works/pi-durable";
|
|
3
4
|
import type { ConversationMetadata, ConversationMetadataPatch } from "./metadata.js";
|
|
4
5
|
/** JSON form of Pi's stored configuration; the host resolves registry names. */
|
|
@@ -8,32 +9,60 @@ export type SerializedAgentChange = {
|
|
|
8
9
|
export type SerializedConversationCreateOptions = Omit<ConversationCreateOptions, "agent" | "init"> & {
|
|
9
10
|
agent?: SerializedAgentChange;
|
|
10
11
|
};
|
|
11
|
-
export type
|
|
12
|
+
export type HttpPiDurableWatchFrame<T = ConversationView> = {
|
|
12
13
|
type: "view";
|
|
13
|
-
view:
|
|
14
|
+
view: T;
|
|
14
15
|
} | {
|
|
15
16
|
type: "ops";
|
|
16
17
|
ops: readonly Op[];
|
|
18
|
+
} | {
|
|
19
|
+
type: "failed";
|
|
20
|
+
error: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "end";
|
|
23
|
+
kind: WatchEnd["kind"];
|
|
24
|
+
error?: string;
|
|
25
|
+
};
|
|
26
|
+
export type HttpPiDurableWatchSubscription = {
|
|
27
|
+
subscriptionId: number;
|
|
28
|
+
id: ConversationId;
|
|
29
|
+
kind: "conversation" | "metadata";
|
|
30
|
+
window?: ConversationWindow;
|
|
17
31
|
};
|
|
18
|
-
export type
|
|
19
|
-
|
|
32
|
+
export type HttpPiDurableWatchEnvelope = {
|
|
33
|
+
subscriptionId: number;
|
|
34
|
+
frame: HttpPiDurableWatchFrame<unknown>;
|
|
35
|
+
};
|
|
36
|
+
export type PiDurableConnectionState = "idle" | "connecting" | "connected" | "reconnecting" | "closed";
|
|
37
|
+
export interface PiDurableWatchOptions {
|
|
20
38
|
window?: () => ConversationWindow | undefined;
|
|
21
|
-
onConnection?: (state:
|
|
22
|
-
|
|
39
|
+
onConnection?: (state: PiDurableConnectionState) => void;
|
|
40
|
+
signal?: AbortSignal;
|
|
23
41
|
}
|
|
24
|
-
export
|
|
42
|
+
export type PiDurableHumanInputDetails = {
|
|
43
|
+
type: "human-input";
|
|
44
|
+
taskId: TaskId;
|
|
45
|
+
prompt: string;
|
|
46
|
+
} & ({
|
|
47
|
+
status: "pending";
|
|
48
|
+
} | {
|
|
49
|
+
status: "answered";
|
|
50
|
+
response: string;
|
|
51
|
+
});
|
|
52
|
+
export interface PiDurableUploadResult {
|
|
25
53
|
name: string;
|
|
26
54
|
content: Exclude<UserInput, string>;
|
|
27
55
|
}
|
|
28
56
|
/** The native operations a browser needs, with server execution context already bound. */
|
|
29
|
-
export interface
|
|
57
|
+
export interface PiDurableClient {
|
|
30
58
|
listConversations(query?: ConversationQuery, limit?: number, cursor?: Cursor): Promise<Page<ConversationRecord, Cursor>>;
|
|
31
59
|
createConversation(options?: SerializedConversationCreateOptions): Promise<ConversationRecord>;
|
|
32
60
|
getConversationView(id: ConversationId): Promise<ConversationView>;
|
|
33
61
|
entries(id: ConversationId, query: Omit<EntryQuery, "conversationId">, limit: number, cursor?: Cursor, signal?: AbortSignal): Promise<Page<EntryRecord, Cursor>>;
|
|
34
62
|
submit(id: ConversationId, submission: SubmissionDraft): Promise<SubmissionRecord>;
|
|
35
63
|
getSubmission(id: SubmissionId): Promise<SubmissionRecord>;
|
|
36
|
-
watch(id: ConversationId,
|
|
64
|
+
watch(id: ConversationId, options?: PiDurableWatchOptions): Promise<WatchHandle<ConversationView>>;
|
|
65
|
+
watchMetadata?(id: ConversationId, signal?: AbortSignal): Promise<WatchHandle<ConversationMetadata>>;
|
|
37
66
|
configure?(id: ConversationId, change: SerializedAgentChange): Promise<void>;
|
|
38
67
|
abort?(id: ConversationId, options?: ConversationAbortOptions): Promise<void>;
|
|
39
68
|
abortSubmission?(id: SubmissionId): ReturnType<Harness["abortSubmission"]>;
|
|
@@ -41,19 +70,19 @@ export interface PiClient {
|
|
|
41
70
|
compact?(id: ConversationId, instructions?: string): Promise<TaskId<CompactionResult>>;
|
|
42
71
|
fork?(id: ConversationId, at: EntryId, options?: SerializedConversationCreateOptions): Promise<ConversationRecord>;
|
|
43
72
|
context?(id: ConversationId, at?: EntryId): Promise<ContextView>;
|
|
44
|
-
uploadFile?(file: File, signal?: AbortSignal): Promise<
|
|
73
|
+
uploadFile?(file: File, signal?: AbortSignal): Promise<PiDurableUploadResult>;
|
|
45
74
|
respondToHumanInput?(taskId: TaskId, response: string): Promise<void>;
|
|
46
75
|
inspect?(): Promise<HarnessInspection>;
|
|
47
|
-
listMetadata?(): Promise<Record<string, ConversationMetadata>>;
|
|
76
|
+
listMetadata?(ids: readonly ConversationId[]): Promise<Record<string, ConversationMetadata>>;
|
|
48
77
|
getMetadata?(id: ConversationId): Promise<ConversationMetadata>;
|
|
49
78
|
updateMetadata?(id: ConversationId, patch: ConversationMetadataPatch): Promise<ConversationMetadata>;
|
|
50
79
|
setFeedback?(id: ConversationId, entryId: EntryId, rating: "positive" | "negative" | null): Promise<ConversationMetadata>;
|
|
51
80
|
}
|
|
52
|
-
export interface
|
|
81
|
+
export interface HttpPiDurableClientOptions {
|
|
53
82
|
baseUrl?: string;
|
|
54
83
|
fetch?: typeof globalThis.fetch;
|
|
55
84
|
eventSource?: (url: string) => EventSource;
|
|
56
85
|
reconnectDelayMs?: number;
|
|
57
86
|
}
|
|
58
|
-
export declare function
|
|
87
|
+
export declare function createHttpPiDurableClient(options?: HttpPiDurableClientOptions): Required<PiDurableClient>;
|
|
59
88
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,EAAE,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EACV,yBAAyB,EACzB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,WAAW,EACX,OAAO,EACP,IAAI,EACJ,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,MAAM,EACN,SAAS,EACT,OAAO,EACP,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAErF,gFAAgF;AAChF,MAAM,MAAM,qBAAqB,GAAG;KAAG,CAAC,IAAI,MAAM,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;CAAE,CAAC;AACvF,MAAM,MAAM,mCAAmC,GAAG,IAAI,CAAC,yBAAyB,EAAE,OAAO,GAAG,MAAM,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAExI,MAAM,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -1,8 +1,50 @@
|
|
|
1
1
|
import { applyImmutable } from "@earendil-works/chord/delta";
|
|
2
|
-
|
|
2
|
+
import { Watch } from "@earendil-works/chord";
|
|
3
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
4
|
+
export function createHttpPiDurableClient(options = {}) {
|
|
3
5
|
const baseUrl = (options.baseUrl ?? "/api").replace(/\/$/, "");
|
|
4
6
|
const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
5
7
|
const openEvents = options.eventSource ?? ((url) => new EventSource(url));
|
|
8
|
+
const subscriptions = new Map();
|
|
9
|
+
let nextSubscriptionId = 0;
|
|
10
|
+
let source;
|
|
11
|
+
let retry;
|
|
12
|
+
let connectQueued = false;
|
|
13
|
+
function scheduleConnect() {
|
|
14
|
+
clearTimeout(retry);
|
|
15
|
+
if (connectQueued)
|
|
16
|
+
return;
|
|
17
|
+
connectQueued = true;
|
|
18
|
+
queueMicrotask(() => { connectQueued = false; connect(); });
|
|
19
|
+
}
|
|
20
|
+
function connect() {
|
|
21
|
+
source?.close();
|
|
22
|
+
source = undefined;
|
|
23
|
+
if (subscriptions.size === 0)
|
|
24
|
+
return;
|
|
25
|
+
for (const subscription of subscriptions.values())
|
|
26
|
+
subscription.connection("connecting");
|
|
27
|
+
const connection = openEvents(url("/watch", { subscriptions: [...subscriptions.values()].map((subscription) => subscription.request()) }));
|
|
28
|
+
source = connection;
|
|
29
|
+
connection.onmessage = (event) => {
|
|
30
|
+
try {
|
|
31
|
+
const { subscriptionId, frame } = JSON.parse(event.data);
|
|
32
|
+
subscriptions.get(subscriptionId)?.receive(frame);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
connection.close();
|
|
36
|
+
for (const subscription of subscriptions.values())
|
|
37
|
+
subscription.receive({ type: "end", kind: "failed", error: error instanceof Error ? error.message : String(error) });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
connection.onerror = () => {
|
|
41
|
+
connection.close();
|
|
42
|
+
for (const subscription of subscriptions.values())
|
|
43
|
+
subscription.connection("reconnecting");
|
|
44
|
+
if (subscriptions.size > 0)
|
|
45
|
+
retry = setTimeout(connect, options.reconnectDelayMs ?? 500);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
6
48
|
function url(path, query = {}) {
|
|
7
49
|
const params = new URLSearchParams();
|
|
8
50
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -38,57 +80,86 @@ export function createHttpPiClient(options = {}) {
|
|
|
38
80
|
fork: (id, at, forkOptions = { ownership: { kind: "ownerless" } }) => post(`/conversations/${id}/fork`, { at, ...forkOptions }),
|
|
39
81
|
context: (id, at) => request(`/conversations/${id}/context`, undefined, { at }),
|
|
40
82
|
uploadFile: (file, signal) => request("/files", { method: "POST", headers: { "content-type": file.type || "application/octet-stream" }, body: file, signal }, { name: file.name }),
|
|
41
|
-
respondToHumanInput: async (taskId, response) => { await post(`/
|
|
83
|
+
respondToHumanInput: async (taskId, response) => { await post(`/human-input/${taskId}`, { response }); },
|
|
42
84
|
inspect: () => request("/inspect"),
|
|
43
|
-
listMetadata: () =>
|
|
85
|
+
listMetadata: (ids) => post("/conversations/metadata", { ids }),
|
|
44
86
|
getMetadata: (id) => request(`/conversations/${id}/metadata`),
|
|
45
87
|
updateMetadata: (id, patch) => request(`/conversations/${id}/metadata`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(patch) }),
|
|
46
88
|
setFeedback: (id, entryId, rating) => post(`/conversations/${id}/feedback`, { entryId, rating }),
|
|
47
|
-
watch(id,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
watch: (id, watchOptions = {}) => openWatch(id, "conversation", watchOptions),
|
|
90
|
+
watchMetadata: (id, signal) => openWatch(id, "metadata", { signal }),
|
|
91
|
+
};
|
|
92
|
+
function openWatch(id, kind, watchOptions) {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const subscriptionId = ++nextSubscriptionId;
|
|
95
|
+
let observation;
|
|
96
|
+
let value;
|
|
97
|
+
function dispose() {
|
|
98
|
+
subscriptions.delete(subscriptionId);
|
|
99
|
+
watchOptions.signal?.removeEventListener("abort", abort);
|
|
100
|
+
scheduleConnect();
|
|
101
|
+
}
|
|
102
|
+
function abort() {
|
|
103
|
+
dispose();
|
|
104
|
+
if (observation)
|
|
105
|
+
observation.abort();
|
|
106
|
+
else
|
|
107
|
+
reject(watchOptions.signal?.reason ?? new DOMException("Observation aborted", "AbortError"));
|
|
108
|
+
}
|
|
109
|
+
subscriptions.set(subscriptionId, {
|
|
110
|
+
request: () => ({ subscriptionId, id, kind, window: watchOptions.window?.() }),
|
|
111
|
+
connection: (state) => watchOptions.onConnection?.(state === "connecting" && observation ? "reconnecting" : state),
|
|
112
|
+
receive(raw) {
|
|
60
113
|
try {
|
|
61
|
-
const frame =
|
|
114
|
+
const frame = raw;
|
|
115
|
+
if (frame.type === "end") {
|
|
116
|
+
dispose();
|
|
117
|
+
watchOptions.onConnection?.("closed");
|
|
118
|
+
const error = new Error(frame.error ?? `Pi observation ended: ${frame.kind}`);
|
|
119
|
+
if (!observation)
|
|
120
|
+
reject(error);
|
|
121
|
+
else
|
|
122
|
+
observation.end(frame.kind === "failed" || frame.kind === "listener_error" ? { kind: frame.kind, error } : { kind: frame.kind });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (frame.type === "failed") {
|
|
126
|
+
if (!observation)
|
|
127
|
+
throw new Error(frame.error);
|
|
128
|
+
observation.fail(new Error(frame.error), BACKGROUND_CONTEXT);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
62
131
|
if (frame.type === "view")
|
|
63
|
-
|
|
132
|
+
value = frame.view;
|
|
64
133
|
else {
|
|
65
|
-
if (!
|
|
134
|
+
if (!observation)
|
|
66
135
|
throw new Error("Pi watch sent changes before its initial view");
|
|
67
|
-
|
|
136
|
+
value = applyImmutable(value, frame.ops);
|
|
137
|
+
}
|
|
138
|
+
if (!observation) {
|
|
139
|
+
observation = new Watch(value, dispose, console.error);
|
|
140
|
+
resolve(observation);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
observation.advance({ kind: "value", value, ops: frame.type === "ops" ? frame.ops : [["r", value]], retired: false }, BACKGROUND_CONTEXT);
|
|
68
144
|
}
|
|
69
|
-
connectedBefore = true;
|
|
70
145
|
watchOptions.onConnection?.("connected");
|
|
71
|
-
onView(view);
|
|
72
146
|
}
|
|
73
|
-
catch (
|
|
74
|
-
|
|
75
|
-
|
|
147
|
+
catch (error) {
|
|
148
|
+
dispose();
|
|
149
|
+
watchOptions.onConnection?.("closed");
|
|
150
|
+
if (observation)
|
|
151
|
+
observation.end({ kind: "failed", error });
|
|
152
|
+
else
|
|
153
|
+
reject(error);
|
|
76
154
|
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
if (watchOptions.signal?.aborted)
|
|
158
|
+
abort();
|
|
159
|
+
else {
|
|
160
|
+
watchOptions.signal?.addEventListener("abort", abort, { once: true });
|
|
161
|
+
scheduleConnect();
|
|
85
162
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
stopped = true;
|
|
89
|
-
clearTimeout(retry);
|
|
90
|
-
source?.close();
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
94
165
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ConversationTranscript, type ConversationId, type EntryId, type SubmissionDraft, type TranscriptView } from "@earendil-works/pi-durable";
|
|
2
|
+
import type { AppendMessage } from "@assistant-ui/react";
|
|
3
|
+
import type { PiDurableClient, PiDurableConnectionState, SerializedAgentChange } from "./client.js";
|
|
4
|
+
import { type ConversationMetadata } from "./metadata.js";
|
|
5
|
+
export interface PiDurableConversationState {
|
|
6
|
+
readonly view: TranscriptView | undefined;
|
|
7
|
+
readonly metadata: ConversationMetadata;
|
|
8
|
+
readonly connection: PiDurableConnectionState;
|
|
9
|
+
readonly isLoading: boolean;
|
|
10
|
+
readonly error: Error | undefined;
|
|
11
|
+
}
|
|
12
|
+
/** Disposable observation and actions permanently bound to one native conversation. */
|
|
13
|
+
export declare class PiDurableConversation {
|
|
14
|
+
#private;
|
|
15
|
+
readonly client: PiDurableClient;
|
|
16
|
+
readonly id: ConversationId;
|
|
17
|
+
readonly transcript: ConversationTranscript;
|
|
18
|
+
constructor(client: PiDurableClient, id: ConversationId, pageSize?: number);
|
|
19
|
+
getSnapshot: () => PiDurableConversationState;
|
|
20
|
+
subscribe: (listener: () => void) => (() => void);
|
|
21
|
+
reportError: (error: unknown) => void;
|
|
22
|
+
start: () => void;
|
|
23
|
+
stop: () => void;
|
|
24
|
+
reload: () => void;
|
|
25
|
+
submit: (draft: SubmissionDraft) => Promise<import("@earendil-works/pi-durable").SubmissionRecord>;
|
|
26
|
+
continue: () => Promise<import("@earendil-works/pi-durable").SubmissionRecord>;
|
|
27
|
+
steer: (content: Extract<SubmissionDraft, {
|
|
28
|
+
type: "input";
|
|
29
|
+
}>["content"]) => Promise<import("@earendil-works/pi-durable").SubmissionRecord>;
|
|
30
|
+
followUp: (content: Extract<SubmissionDraft, {
|
|
31
|
+
type: "input";
|
|
32
|
+
}>["content"]) => Promise<import("@earendil-works/pi-durable").SubmissionRecord>;
|
|
33
|
+
cancel: () => Promise<void>;
|
|
34
|
+
abortSubmission: (id: Parameters<NonNullable<PiDurableClient["abortSubmission"]>>[0]) => Promise<"aborted" | "already_placed" | "not_found" | "settled">;
|
|
35
|
+
configure: (change: SerializedAgentChange) => Promise<void>;
|
|
36
|
+
reset: (handoff?: string) => Promise<void>;
|
|
37
|
+
compact: (instructions?: string) => Promise<import("@earendil-works/pi-durable").TaskId<import("@earendil-works/pi-durable").CompactionResult>>;
|
|
38
|
+
forkAt: (at: EntryId) => Promise<import("@earendil-works/pi-durable").ConversationRecord>;
|
|
39
|
+
edit: (message: AppendMessage) => Promise<import("@earendil-works/pi-durable").ConversationRecord>;
|
|
40
|
+
regenerate: (parentId: string | null) => Promise<import("@earendil-works/pi-durable").ConversationRecord>;
|
|
41
|
+
loadEarlier: () => Promise<void>;
|
|
42
|
+
setFeedback: (messageId: string, rating: "positive" | "negative") => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=conversation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../src/conversation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,KAAK,cAAc,EAAyB,KAAK,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACzK,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,EAA+B,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGvF,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,wBAAwB,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;CACnC;AAED,uFAAuF;AACvF,qBAAa,qBAAqB;;IAMpB,QAAQ,CAAC,MAAM,EAAE,eAAe;IAAE,QAAQ,CAAC,EAAE,EAAE,cAAc;IALzE,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC;IAK5C,YAAqB,MAAM,EAAE,eAAe,EAAW,EAAE,EAAE,cAAc,EAAE,QAAQ,CAAC,EAAE,MAAM,EAE3F;IAED,WAAW,QAAO,0BAA0B,CAAgB;IAC5D,SAAS,aAAc,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAG9C;IAOF,WAAW,UAAW,OAAO,UAE3B;IAEF,KAAK,QAAO,IAAI,CAkCd;IAOF,IAAI,QAAO,IAAI,CAGb;IAEF,MAAM,QAAO,IAAI,CAAmC;IACpD,MAAM,UAAW,eAAe,oEAAwC;IACxE,QAAQ,uEAA2C;IACnD,KAAK,YAAa,OAAO,CAAC,eAAe,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,SAAS,CAAC,oEAAgE;IACxI,QAAQ,YAAa,OAAO,CAAC,eAAe,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,SAAS,CAAC,oEAAmE;IAE9I,MAAM,sBAGJ;IACF,eAAe,OAAQ,UAAU,CAAC,WAAW,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,qEAGnF;IACF,SAAS,WAAY,qBAAqB,mBAGxC;IACF,KAAK,aAAc,MAAM,mBAGvB;IACF,OAAO,kBAAmB,MAAM,iHAG9B;IACF,MAAM,OAAQ,OAAO,sEAGnB;IAEF,IAAI,YAAmB,aAAa,sEAOlC;IAEF,UAAU,aAAoB,MAAM,GAAG,IAAI,sEAUzC;IAEF,WAAW,sBAGT;IAEF,WAAW,cAAqB,MAAM,UAAU,UAAU,GAAG,UAAU,mBAGrE;CACH"}
|