@substrat-run/engine-assistant 0.0.0-stage → 0.1.28
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 +661 -0
- package/README.md +125 -2
- package/dist/entities.d.ts +174 -0
- package/dist/entities.d.ts.map +1 -0
- package/dist/entities.js +110 -0
- package/dist/entities.js.map +1 -0
- package/dist/events.d.ts +106 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +57 -0
- package/dist/events.js.map +1 -0
- package/dist/files.d.ts +32 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +69 -0
- package/dist/files.js.map +1 -0
- package/dist/index.d.ts +356 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +811 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +12 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +19 -0
- package/dist/model.js.map +1 -0
- package/dist/operations.d.ts +500 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +106 -0
- package/dist/operations.js.map +1 -0
- package/dist/seam.d.ts +5 -0
- package/dist/seam.d.ts.map +1 -0
- package/dist/seam.js +15 -0
- package/dist/seam.js.map +1 -0
- package/dist/shapes.d.ts +428 -0
- package/dist/shapes.d.ts.map +1 -0
- package/dist/shapes.js +134 -0
- package/dist/shapes.js.map +1 -0
- package/package.json +51 -3
package/README.md
CHANGED
|
@@ -1,3 +1,126 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @substrat-run/engine-assistant
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The record of a chat between a person and an assistant, for [Substrat](https://github.com/substrat-run/substrat).
|
|
4
|
+
An append-only event log, a single-writer run lease, and tool calls whose gated writes need an
|
|
5
|
+
approval bound to the exact input.
|
|
6
|
+
|
|
7
|
+
Configured for **public npm publication**; the first npm release is pending. Nothing composes
|
|
8
|
+
it yet; it is the first part of the assistant described in [#2283](https://github.com/substrat-run/substrat/issues/2283).
|
|
9
|
+
|
|
10
|
+
## How a host uses it
|
|
11
|
+
|
|
12
|
+
The engine is composed **by call** and never talks to a model. A host runs the loop and calls
|
|
13
|
+
the in-scope functions inside its own transaction, as a principal holding the right key:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { assistantModule, sendMessage, acquireRun, appendEvents, claimExecution, resolveApproval } from '@substrat-run/engine-assistant';
|
|
17
|
+
|
|
18
|
+
host.registerModule(assistantModule);
|
|
19
|
+
|
|
20
|
+
// A person (assistant:use) says something, which begins a turn.
|
|
21
|
+
sendMessage(ctx, { threadId, text });
|
|
22
|
+
|
|
23
|
+
// The host (assistant:run) takes the lease and appends what the assistant did.
|
|
24
|
+
acquireRun(ctx, { threadId, runId });
|
|
25
|
+
await appendEvents(ctx, { threadId, runId, events: [{ t: 'text-start', pid: 'p1' }, /* … */] });
|
|
26
|
+
|
|
27
|
+
// A change waits for approval. The request carries what it binds and their digest, with the
|
|
28
|
+
// thread's owner as the approver:
|
|
29
|
+
// digest = await approvalDigest({ version, approver: threadOwner, thread: threadId, step,
|
|
30
|
+
// call: callId, operation, expiresAt, nonce, input })
|
|
31
|
+
// { t: 'approval-request', callId, step, tool, operation, version, input, expiresAt, nonce, digest, preview }
|
|
32
|
+
// The person approves with the digest they were shown (assistant:use, as the owner), on a
|
|
33
|
+
// route the loop cannot call; `version` is the deployed version the HOST serves the answer under.
|
|
34
|
+
await resolveApproval(ctx, { threadId, callId, digest, approved: true, version });
|
|
35
|
+
|
|
36
|
+
// Before running the operation, the host claims its one execution with what it is about to run.
|
|
37
|
+
await claimExecution(ctx, { threadId, runId, callId, operation, version, input });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`approvalDigest` and the event vocabulary (`assistantStreamBody`) are in
|
|
41
|
+
`@substrat-run/contracts/assistant-stream`.
|
|
42
|
+
|
|
43
|
+
**After every answer, the host runs.** An `approval-resolved`, approval or denial, resumes the
|
|
44
|
+
turn rather than ending it, so the owner gets `turn_in_progress` until a run writes
|
|
45
|
+
`turn-finish` or `turn-error`. A host therefore takes the lease after every answer, a denial
|
|
46
|
+
included, and ends the turn (`turn-finish: done` after a denial, or `turn-error` if it cannot
|
|
47
|
+
go on). If a turn is abandoned, because the host never came back, a later run takes the lease
|
|
48
|
+
once the holder is stale and closes it the same way. The owner has no way to end a turn
|
|
49
|
+
without a run; closing the thread is their only way out.
|
|
50
|
+
|
|
51
|
+
## Two keys
|
|
52
|
+
|
|
53
|
+
`assistant:use` is a person's: open and read their own threads, say something, approve or deny.
|
|
54
|
+
`assistant:run` is for a **system principal only** (the assistant host's), never a role a person
|
|
55
|
+
holds: it writes the assistant's side of the record. The manifest declares it `holder: 'system'`,
|
|
56
|
+
and `pnpm lint:permissions` refuses a role or entity grant shape that grants it. `assistant:audit`
|
|
57
|
+
reads other principals' threads and never writes, and only together with `assistant:use`, which
|
|
58
|
+
every read checks first.
|
|
59
|
+
|
|
60
|
+
## Files for imports and comparisons
|
|
61
|
+
|
|
62
|
+
The host-facing entry point `@substrat-run/engine-assistant/files` accepts `.csv`, `.tsv`
|
|
63
|
+
and `.xlsx` uploads, up to 10 MiB each. A message can reference up to five files from its
|
|
64
|
+
own thread. Text is optional when files are present; existing text-only calls are unchanged.
|
|
65
|
+
Older `.xls` workbooks must first be saved as `.xlsx`.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { assistantFiles } from '@substrat-run/engine-assistant/files';
|
|
69
|
+
import { readSpreadsheet } from '@substrat-run/attachment-extractors';
|
|
70
|
+
|
|
71
|
+
// HOST code: both handles MUST address the same scope. `person` is an authenticated
|
|
72
|
+
// person's ScopeStub; `storage` is the assistant host's ScopeAttachments, holding
|
|
73
|
+
// assistant:run through its system role. Never hand that storage handle to a client.
|
|
74
|
+
const files = assistantFiles(person, storage);
|
|
75
|
+
const uploaded = await files.upload({ threadId, filename: 'items.csv', body: bytes });
|
|
76
|
+
await person.invoke('assistant/send-message', {
|
|
77
|
+
threadId, text: 'Compare these rows with the current items', attachmentIds: [uploaded.id],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// In a host's file-reading tool, authorize before opening bytes, then read structured rows.
|
|
81
|
+
const opened = await files.open(threadId, uploaded.id);
|
|
82
|
+
const spreadsheet = await readSpreadsheet({
|
|
83
|
+
body: opened.body, filename: opened.record.filename, contentType: opened.record.contentType,
|
|
84
|
+
});
|
|
85
|
+
// spreadsheet.sheets: [{ name, rows: [{ number: 1, cells: ['code', 'amount'] }, …] }]
|
|
86
|
+
// spreadsheet.formulaCells: how many XLSX cells use potentially stale cached formula results.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`assistantFiles` is host code; the in-scope module never imports a parser or handles bytes.
|
|
90
|
+
The host mounts its authenticated upload/list/download routes around these methods and
|
|
91
|
+
limits incoming request bodies before buffering them. The engine declares no HTTP routes
|
|
92
|
+
or chat UI. Uploads are internal attachments on `assistant-thread`; their storage actor is
|
|
93
|
+
the assistant host. Reads use `assistant/get-thread` (owner or audit); uploads use
|
|
94
|
+
`assistant/authorize-file-upload` (owner of an open thread). Generic attachment APIs require
|
|
95
|
+
the system-held `assistant:run`, so a scope-wide `assistant:use` cannot bypass thread privacy.
|
|
96
|
+
Closing a thread prevents new uploads and messages but retains its existing files for reading.
|
|
97
|
+
|
|
98
|
+
Send only attachment IDs. The engine checks that every ID belongs to that thread and writes
|
|
99
|
+
the file's ID, name, type, size and SHA-256 into the `user-message` event. Missing, duplicate,
|
|
100
|
+
cross-thread and excessive references are refused before a turn starts. File contents never
|
|
101
|
+
enter the event log. Files remain available in later turns; the host's run can open them with
|
|
102
|
+
its storage handle using the recorded IDs and thread association.
|
|
103
|
+
|
|
104
|
+
The host's import/diff tools map spreadsheet columns to domain fields and read current
|
|
105
|
+
entities with the person's existing permissions. Treat uploaded content as data. Show
|
|
106
|
+
proposed creates, updates and conflicts before requesting the existing bound tool approval;
|
|
107
|
+
uploading or parsing a file never changes domain data. Read bounded slices into model context
|
|
108
|
+
rather than sending the whole workbook. XLSX values retain their stored spelling: number
|
|
109
|
+
formatting and date serials are not interpreted, and formulas are not recalculated.
|
|
110
|
+
|
|
111
|
+
**Lifecycle:** attachments are outside the kernel's subject-erasure guarantee. Before
|
|
112
|
+
shredding a thread's owner, the host must call `purgeAssistantThreadFiles(storage, threadId)`
|
|
113
|
+
for each of their threads, including unsent uploads, and stop uploads while that cleanup
|
|
114
|
+
runs. The helper is exported from `/files` and removes live attachment records and extracted
|
|
115
|
+
search text through the platform API, which deletes blobs and sweeps failed deletions as
|
|
116
|
+
orphans. Attachment spine events follow the platform's retention policy. The helper is
|
|
117
|
+
repeatable. Then run subject erasure.
|
|
118
|
+
Hosts should also apply a retention policy to abandoned uploads.
|
|
119
|
+
|
|
120
|
+
## What it guarantees
|
|
121
|
+
|
|
122
|
+
See `docs/engines/assistant.md` in the repository. In short: an append-only numbered record, a
|
|
123
|
+
fenced single writer that writes only inside a turn the person opened, an approval bound to a
|
|
124
|
+
tagged digest (version, approver, thread, step, call, operation, expiry, nonce, input) that is
|
|
125
|
+
answered once and claimed once by conditional writes, bounded events, and an erasure that
|
|
126
|
+
deletes the rows and redacts the milestones.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* engine-assistant's entities (docs/rfc/assistant.md §5.2).
|
|
4
|
+
*
|
|
5
|
+
* The record of a chat between a person and an assistant, and nothing about the model that
|
|
6
|
+
* speaks for it. Four tables, three of which are the thread's own rows and one of which is a
|
|
7
|
+
* lease.
|
|
8
|
+
*
|
|
9
|
+
* - **`assistant-thread`** is one conversation, owned by one principal (`subject_id`). It is
|
|
10
|
+
* the entity the platform points at: every event this engine emits names it.
|
|
11
|
+
* - **`assistant-event`** is one event of the thread, in the stream protocol's own shape
|
|
12
|
+
* (`assistantStreamBody`), numbered `seq` from 1 per thread. APPEND-ONLY: nothing here is
|
|
13
|
+
* updated or deleted by the engine. `seq` is the cursor a client resumes after. `body` is
|
|
14
|
+
* the event serialised, without its `seq`, and is where the person's words live.
|
|
15
|
+
* - **`assistant-run`** is the single-writer lease: which run may append the assistant's
|
|
16
|
+
* events to a thread right now, and when it last beat. Not an entity of the person's, but
|
|
17
|
+
* it carries `subject_id` so an erasure leaves no row naming a thread that is gone.
|
|
18
|
+
* - **`assistant-tool-call`** is the state a tool call passes through, which is where the
|
|
19
|
+
* engine's central invariant lives: a gated write runs only from `approved`, once, with the
|
|
20
|
+
* input whose digest the user saw.
|
|
21
|
+
*
|
|
22
|
+
* Erasure (K-46). Everything that holds what the person said or what the assistant did for
|
|
23
|
+
* them is theirs whole, so each table names `subject_id` as its subject column and erases by
|
|
24
|
+
* `delete`. The rows have no `id` of their own to survive an erasure with, and nothing about
|
|
25
|
+
* a thread is meaningful once its owner is gone. The events this engine EMITS carry no text,
|
|
26
|
+
* no tool input or output and no digest of one, and every one is `pseudonymous` with the
|
|
27
|
+
* thread's subject as its `subjectId`, so the same erasure redacts their payloads too.
|
|
28
|
+
*/
|
|
29
|
+
export declare const assistantEntities: {
|
|
30
|
+
readonly 'assistant-thread': {
|
|
31
|
+
readonly table: "assistant_threads";
|
|
32
|
+
readonly fields: z.ZodObject<{
|
|
33
|
+
id: z.ZodString;
|
|
34
|
+
subject_id: z.ZodString;
|
|
35
|
+
title: z.ZodNullable<z.ZodString>;
|
|
36
|
+
status: z.ZodEnum<{
|
|
37
|
+
closed: "closed";
|
|
38
|
+
open: "open";
|
|
39
|
+
}>;
|
|
40
|
+
created_at: z.ZodString;
|
|
41
|
+
last_event_at: z.ZodString;
|
|
42
|
+
closed_at: z.ZodNullable<z.ZodString>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
readonly erasable: readonly ["title"];
|
|
45
|
+
readonly erasure: {
|
|
46
|
+
readonly subjects: readonly ["subject_id"];
|
|
47
|
+
readonly mode: "delete";
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
readonly 'assistant-event': {
|
|
51
|
+
readonly table: "assistant_events";
|
|
52
|
+
readonly fields: z.ZodObject<{
|
|
53
|
+
thread_id: z.ZodString;
|
|
54
|
+
seq: z.ZodNumber;
|
|
55
|
+
type: z.ZodString;
|
|
56
|
+
body: z.ZodString;
|
|
57
|
+
subject_id: z.ZodString;
|
|
58
|
+
created_at: z.ZodString;
|
|
59
|
+
}, z.core.$strip>;
|
|
60
|
+
readonly primaryKey: readonly ["thread_id", "seq"];
|
|
61
|
+
readonly erasable: readonly ["body"];
|
|
62
|
+
readonly erasure: {
|
|
63
|
+
readonly subjects: readonly ["subject_id"];
|
|
64
|
+
readonly mode: "delete";
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
readonly 'assistant-run': {
|
|
68
|
+
readonly table: "assistant_runs";
|
|
69
|
+
readonly fields: z.ZodObject<{
|
|
70
|
+
thread_id: z.ZodString;
|
|
71
|
+
run_id: z.ZodString;
|
|
72
|
+
subject_id: z.ZodString;
|
|
73
|
+
beat_at: z.ZodString;
|
|
74
|
+
}, z.core.$strip>;
|
|
75
|
+
readonly primaryKey: readonly ["thread_id"];
|
|
76
|
+
readonly erasable: readonly ["subject_id"];
|
|
77
|
+
readonly erasure: {
|
|
78
|
+
readonly subjects: readonly ["subject_id"];
|
|
79
|
+
readonly mode: "delete";
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
readonly 'assistant-tool-call': {
|
|
83
|
+
readonly table: "assistant_tool_calls";
|
|
84
|
+
readonly fields: z.ZodObject<{
|
|
85
|
+
thread_id: z.ZodString;
|
|
86
|
+
call_id: z.ZodString;
|
|
87
|
+
tool: z.ZodString;
|
|
88
|
+
state: z.ZodEnum<{
|
|
89
|
+
approved: "approved";
|
|
90
|
+
"awaiting-approval": "awaiting-approval";
|
|
91
|
+
denied: "denied";
|
|
92
|
+
done: "done";
|
|
93
|
+
executing: "executing";
|
|
94
|
+
failed: "failed";
|
|
95
|
+
running: "running";
|
|
96
|
+
}>;
|
|
97
|
+
digest: z.ZodNullable<z.ZodString>;
|
|
98
|
+
step: z.ZodNullable<z.ZodString>;
|
|
99
|
+
operation: z.ZodNullable<z.ZodString>;
|
|
100
|
+
version: z.ZodNullable<z.ZodString>;
|
|
101
|
+
expires_at: z.ZodNullable<z.ZodString>;
|
|
102
|
+
nonce: z.ZodNullable<z.ZodString>;
|
|
103
|
+
request_seq: z.ZodNullable<z.ZodNumber>;
|
|
104
|
+
decided_by: z.ZodNullable<z.ZodString>;
|
|
105
|
+
decided_at: z.ZodNullable<z.ZodString>;
|
|
106
|
+
subject_id: z.ZodString;
|
|
107
|
+
created_at: z.ZodString;
|
|
108
|
+
updated_at: z.ZodString;
|
|
109
|
+
}, z.core.$strip>;
|
|
110
|
+
readonly primaryKey: readonly ["thread_id", "call_id"];
|
|
111
|
+
readonly erasable: readonly ["subject_id", "decided_by"];
|
|
112
|
+
readonly erasure: {
|
|
113
|
+
readonly subjects: readonly ["subject_id"];
|
|
114
|
+
readonly mode: "delete";
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
/** One thread, as stored. */
|
|
119
|
+
export declare const threadRow: z.ZodObject<{
|
|
120
|
+
id: z.ZodString;
|
|
121
|
+
subject_id: z.ZodString;
|
|
122
|
+
title: z.ZodNullable<z.ZodString>;
|
|
123
|
+
status: z.ZodEnum<{
|
|
124
|
+
closed: "closed";
|
|
125
|
+
open: "open";
|
|
126
|
+
}>;
|
|
127
|
+
created_at: z.ZodString;
|
|
128
|
+
last_event_at: z.ZodString;
|
|
129
|
+
closed_at: z.ZodNullable<z.ZodString>;
|
|
130
|
+
}, z.core.$strip>;
|
|
131
|
+
/** One event of a thread, as stored. */
|
|
132
|
+
export declare const eventRow: z.ZodObject<{
|
|
133
|
+
thread_id: z.ZodString;
|
|
134
|
+
seq: z.ZodNumber;
|
|
135
|
+
type: z.ZodString;
|
|
136
|
+
body: z.ZodString;
|
|
137
|
+
subject_id: z.ZodString;
|
|
138
|
+
created_at: z.ZodString;
|
|
139
|
+
}, z.core.$strip>;
|
|
140
|
+
/** The lease on a thread, as stored. */
|
|
141
|
+
export declare const runRow: z.ZodObject<{
|
|
142
|
+
thread_id: z.ZodString;
|
|
143
|
+
run_id: z.ZodString;
|
|
144
|
+
subject_id: z.ZodString;
|
|
145
|
+
beat_at: z.ZodString;
|
|
146
|
+
}, z.core.$strip>;
|
|
147
|
+
/** One tool call's state, as stored. */
|
|
148
|
+
export declare const toolCallRow: z.ZodObject<{
|
|
149
|
+
thread_id: z.ZodString;
|
|
150
|
+
call_id: z.ZodString;
|
|
151
|
+
tool: z.ZodString;
|
|
152
|
+
state: z.ZodEnum<{
|
|
153
|
+
approved: "approved";
|
|
154
|
+
"awaiting-approval": "awaiting-approval";
|
|
155
|
+
denied: "denied";
|
|
156
|
+
done: "done";
|
|
157
|
+
executing: "executing";
|
|
158
|
+
failed: "failed";
|
|
159
|
+
running: "running";
|
|
160
|
+
}>;
|
|
161
|
+
digest: z.ZodNullable<z.ZodString>;
|
|
162
|
+
step: z.ZodNullable<z.ZodString>;
|
|
163
|
+
operation: z.ZodNullable<z.ZodString>;
|
|
164
|
+
version: z.ZodNullable<z.ZodString>;
|
|
165
|
+
expires_at: z.ZodNullable<z.ZodString>;
|
|
166
|
+
nonce: z.ZodNullable<z.ZodString>;
|
|
167
|
+
request_seq: z.ZodNullable<z.ZodNumber>;
|
|
168
|
+
decided_by: z.ZodNullable<z.ZodString>;
|
|
169
|
+
decided_at: z.ZodNullable<z.ZodString>;
|
|
170
|
+
subject_id: z.ZodString;
|
|
171
|
+
created_at: z.ZodString;
|
|
172
|
+
updated_at: z.ZodString;
|
|
173
|
+
}, z.core.$strip>;
|
|
174
|
+
//# sourceMappingURL=entities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../src/entities.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuE5B,CAAC;AAEH,6BAA6B;AAC7B,eAAO,MAAM,SAAS;;;;;;;;;;;iBAA+C,CAAC;AACtE,wCAAwC;AACxC,eAAO,MAAM,QAAQ;;;;;;;iBAA8C,CAAC;AACpE,wCAAwC;AACxC,eAAO,MAAM,MAAM;;;;;iBAA4C,CAAC;AAChE,wCAAwC;AACxC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;iBAAkD,CAAC"}
|
package/dist/entities.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { defineEntities } from '@substrat-run/contracts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* engine-assistant's entities (docs/rfc/assistant.md §5.2).
|
|
5
|
+
*
|
|
6
|
+
* The record of a chat between a person and an assistant, and nothing about the model that
|
|
7
|
+
* speaks for it. Four tables, three of which are the thread's own rows and one of which is a
|
|
8
|
+
* lease.
|
|
9
|
+
*
|
|
10
|
+
* - **`assistant-thread`** is one conversation, owned by one principal (`subject_id`). It is
|
|
11
|
+
* the entity the platform points at: every event this engine emits names it.
|
|
12
|
+
* - **`assistant-event`** is one event of the thread, in the stream protocol's own shape
|
|
13
|
+
* (`assistantStreamBody`), numbered `seq` from 1 per thread. APPEND-ONLY: nothing here is
|
|
14
|
+
* updated or deleted by the engine. `seq` is the cursor a client resumes after. `body` is
|
|
15
|
+
* the event serialised, without its `seq`, and is where the person's words live.
|
|
16
|
+
* - **`assistant-run`** is the single-writer lease: which run may append the assistant's
|
|
17
|
+
* events to a thread right now, and when it last beat. Not an entity of the person's, but
|
|
18
|
+
* it carries `subject_id` so an erasure leaves no row naming a thread that is gone.
|
|
19
|
+
* - **`assistant-tool-call`** is the state a tool call passes through, which is where the
|
|
20
|
+
* engine's central invariant lives: a gated write runs only from `approved`, once, with the
|
|
21
|
+
* input whose digest the user saw.
|
|
22
|
+
*
|
|
23
|
+
* Erasure (K-46). Everything that holds what the person said or what the assistant did for
|
|
24
|
+
* them is theirs whole, so each table names `subject_id` as its subject column and erases by
|
|
25
|
+
* `delete`. The rows have no `id` of their own to survive an erasure with, and nothing about
|
|
26
|
+
* a thread is meaningful once its owner is gone. The events this engine EMITS carry no text,
|
|
27
|
+
* no tool input or output and no digest of one, and every one is `pseudonymous` with the
|
|
28
|
+
* thread's subject as its `subjectId`, so the same erasure redacts their payloads too.
|
|
29
|
+
*/
|
|
30
|
+
export const assistantEntities = defineEntities({
|
|
31
|
+
'assistant-thread': {
|
|
32
|
+
table: 'assistant_threads',
|
|
33
|
+
fields: z.object({
|
|
34
|
+
id: z.string(),
|
|
35
|
+
subject_id: z.string(),
|
|
36
|
+
title: z.string().nullable(),
|
|
37
|
+
status: z.enum(['open', 'closed']),
|
|
38
|
+
created_at: z.string(),
|
|
39
|
+
last_event_at: z.string(),
|
|
40
|
+
closed_at: z.string().nullable(),
|
|
41
|
+
}),
|
|
42
|
+
erasable: ['title'],
|
|
43
|
+
erasure: { subjects: ['subject_id'], mode: 'delete' },
|
|
44
|
+
},
|
|
45
|
+
'assistant-event': {
|
|
46
|
+
table: 'assistant_events',
|
|
47
|
+
fields: z.object({
|
|
48
|
+
thread_id: z.string(),
|
|
49
|
+
seq: z.number(),
|
|
50
|
+
type: z.string(),
|
|
51
|
+
body: z.string(),
|
|
52
|
+
subject_id: z.string(),
|
|
53
|
+
created_at: z.string(),
|
|
54
|
+
}),
|
|
55
|
+
primaryKey: ['thread_id', 'seq'],
|
|
56
|
+
erasable: ['body'],
|
|
57
|
+
erasure: { subjects: ['subject_id'], mode: 'delete' },
|
|
58
|
+
},
|
|
59
|
+
'assistant-run': {
|
|
60
|
+
table: 'assistant_runs',
|
|
61
|
+
fields: z.object({
|
|
62
|
+
thread_id: z.string(),
|
|
63
|
+
run_id: z.string(),
|
|
64
|
+
subject_id: z.string(),
|
|
65
|
+
beat_at: z.string(),
|
|
66
|
+
}),
|
|
67
|
+
primaryKey: ['thread_id'],
|
|
68
|
+
// Holds no words, but the principal's id says whose thread this lease was on.
|
|
69
|
+
erasable: ['subject_id'],
|
|
70
|
+
erasure: { subjects: ['subject_id'], mode: 'delete' },
|
|
71
|
+
},
|
|
72
|
+
'assistant-tool-call': {
|
|
73
|
+
table: 'assistant_tool_calls',
|
|
74
|
+
fields: z.object({
|
|
75
|
+
thread_id: z.string(),
|
|
76
|
+
call_id: z.string(),
|
|
77
|
+
tool: z.string(),
|
|
78
|
+
state: z.enum(['running', 'awaiting-approval', 'approved', 'denied', 'executing', 'done', 'failed']),
|
|
79
|
+
digest: z.string().nullable(),
|
|
80
|
+
// What the approval request bound (`approvalDigest`), kept so an answer and a claim can
|
|
81
|
+
// recompute the digest rather than trust one. Set together with `digest`, or all null.
|
|
82
|
+
// The input itself is not copied here: `request_seq` points at the request event.
|
|
83
|
+
step: z.string().nullable(),
|
|
84
|
+
operation: z.string().nullable(),
|
|
85
|
+
version: z.string().nullable(),
|
|
86
|
+
expires_at: z.string().nullable(),
|
|
87
|
+
nonce: z.string().nullable(),
|
|
88
|
+
request_seq: z.number().nullable(),
|
|
89
|
+
decided_by: z.string().nullable(),
|
|
90
|
+
decided_at: z.string().nullable(),
|
|
91
|
+
subject_id: z.string(),
|
|
92
|
+
created_at: z.string(),
|
|
93
|
+
updated_at: z.string(),
|
|
94
|
+
}),
|
|
95
|
+
primaryKey: ['thread_id', 'call_id'],
|
|
96
|
+
// The tool name and the digest are not the person's, but who the call was for, and who
|
|
97
|
+
// decided it, are.
|
|
98
|
+
erasable: ['subject_id', 'decided_by'],
|
|
99
|
+
erasure: { subjects: ['subject_id'], mode: 'delete' },
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
/** One thread, as stored. */
|
|
103
|
+
export const threadRow = assistantEntities['assistant-thread'].fields;
|
|
104
|
+
/** One event of a thread, as stored. */
|
|
105
|
+
export const eventRow = assistantEntities['assistant-event'].fields;
|
|
106
|
+
/** The lease on a thread, as stored. */
|
|
107
|
+
export const runRow = assistantEntities['assistant-run'].fields;
|
|
108
|
+
/** One tool call's state, as stored. */
|
|
109
|
+
export const toolCallRow = assistantEntities['assistant-tool-call'].fields;
|
|
110
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.js","sourceRoot":"","sources":["../src/entities.ts"],"names":[],"mappings":"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"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine-assistant's event contract (#696) — what a VERTICAL imports so that consuming this
|
|
3
|
+
* engine by event is checked rather than guessed.
|
|
4
|
+
*
|
|
5
|
+
* Milestones only. The thread's own record is `assistant_events`; these are what the rest of
|
|
6
|
+
* the platform learns about it. A text delta is not a platform event: forty of them make one
|
|
7
|
+
* answer, and an outbox row each would be the record written twice.
|
|
8
|
+
*
|
|
9
|
+
* **No text and no tool data ride an event.** A message, a tool input and a tool output are
|
|
10
|
+
* the person's data. So a payload names the thread, the call and the tool, and nothing else:
|
|
11
|
+
* not the approval's `preview` wording, which can restate what the person asked for, and not
|
|
12
|
+
* its digest either — a SHA-256 of a small input is recovered by guessing the input, so it
|
|
13
|
+
* is the input. Nor does a payload name the person: who a thread belongs to is the
|
|
14
|
+
* envelope's `subjectId`.
|
|
15
|
+
*
|
|
16
|
+
* **Every milestone is `pseudonymous`, keyed to the thread's subject** (`emitAssistantEvent`
|
|
17
|
+
* makes that the only way to emit one). A thread id names one person's conversation, so the
|
|
18
|
+
* outbox rows that carry it are that person's; with a `subjectId`, an erasure (K-46) redacts
|
|
19
|
+
* their payloads along with the thread's own rows.
|
|
20
|
+
*
|
|
21
|
+
* VERTICAL-FACING ONLY. A sibling engine consuming one of these must NOT import it (R1,
|
|
22
|
+
* star topology).
|
|
23
|
+
*
|
|
24
|
+
* Additive, like every other engine surface: a new event type may appear, an existing
|
|
25
|
+
* payload field does not change shape without a `schemaVersion` bump.
|
|
26
|
+
*/
|
|
27
|
+
import { type DomainEventInput } from '@substrat-run/contracts';
|
|
28
|
+
import type { OperationContext } from '@substrat-run/kernel';
|
|
29
|
+
/** A thread was opened. Whose it is, is the envelope's `subjectId`. */
|
|
30
|
+
export interface AssistantThreadCreatedPayload {
|
|
31
|
+
threadId: string;
|
|
32
|
+
}
|
|
33
|
+
/** The user said something and a turn began. */
|
|
34
|
+
export interface AssistantTurnStartedPayload {
|
|
35
|
+
threadId: string;
|
|
36
|
+
}
|
|
37
|
+
/** A run came to rest on a turn: done, waiting for an approval, stopped, or out of budget. */
|
|
38
|
+
export interface AssistantTurnFinishedPayload {
|
|
39
|
+
threadId: string;
|
|
40
|
+
reason: 'done' | 'awaiting-approval' | 'stopped' | 'budget';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A gated tool call is waiting for the user. The digest it is bound to stays in the record
|
|
44
|
+
* (`assistant_events`), never here: it is a hash of the person's input.
|
|
45
|
+
*/
|
|
46
|
+
export interface AssistantApprovalRequestedPayload {
|
|
47
|
+
threadId: string;
|
|
48
|
+
callId: string;
|
|
49
|
+
tool: string;
|
|
50
|
+
}
|
|
51
|
+
/** The user approved or denied a gated tool call. */
|
|
52
|
+
export interface AssistantApprovalResolvedPayload {
|
|
53
|
+
threadId: string;
|
|
54
|
+
callId: string;
|
|
55
|
+
tool: string;
|
|
56
|
+
approved: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** The thread was closed; nothing more is appended. */
|
|
59
|
+
export interface AssistantThreadClosedPayload {
|
|
60
|
+
threadId: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* engine-assistant's event contract — the six event types this engine emits and the payload
|
|
64
|
+
* each one carries. Satisfies the kernel's `EventContract`.
|
|
65
|
+
*/
|
|
66
|
+
export type AssistantEvents = {
|
|
67
|
+
events: {
|
|
68
|
+
'assistant.thread-created': AssistantThreadCreatedPayload;
|
|
69
|
+
'assistant.turn-started': AssistantTurnStartedPayload;
|
|
70
|
+
'assistant.turn-finished': AssistantTurnFinishedPayload;
|
|
71
|
+
'assistant.approval-requested': AssistantApprovalRequestedPayload;
|
|
72
|
+
'assistant.approval-resolved': AssistantApprovalResolvedPayload;
|
|
73
|
+
'assistant.thread-closed': AssistantThreadClosedPayload;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
/** Every event type this engine emits. */
|
|
77
|
+
export type AssistantEventType = keyof AssistantEvents['events'];
|
|
78
|
+
/**
|
|
79
|
+
* Every event type this engine emits, and the `schemaVersion` each is emitted at — the ONE
|
|
80
|
+
* home for that number (#1597). `emitAssistantEvent` stamps it and the manifest's `emits` is
|
|
81
|
+
* derived from it, so the two cannot drift. Bumping a version is K-39's REPLACE.
|
|
82
|
+
*/
|
|
83
|
+
export declare const assistantEventVersions: {
|
|
84
|
+
readonly 'assistant.thread-created': 1;
|
|
85
|
+
readonly 'assistant.turn-started': 1;
|
|
86
|
+
readonly 'assistant.turn-finished': 1;
|
|
87
|
+
readonly 'assistant.approval-requested': 1;
|
|
88
|
+
readonly 'assistant.approval-resolved': 1;
|
|
89
|
+
readonly 'assistant.thread-closed': 1;
|
|
90
|
+
};
|
|
91
|
+
/** The manifest's `events.emits`, read off {@link assistantEventVersions} — never hand-declared. */
|
|
92
|
+
export declare const assistantEmitDeclarations: {
|
|
93
|
+
type: string;
|
|
94
|
+
schemaVersion: number;
|
|
95
|
+
}[];
|
|
96
|
+
/**
|
|
97
|
+
* `ctx.emit`, with the event type and its payload welded together, so the map above is the
|
|
98
|
+
* source rather than a description nothing holds in agreement. It stamps `schemaVersion`, and
|
|
99
|
+
* makes every milestone `pseudonymous` under `subject` — the thread's owner — so an erasure
|
|
100
|
+
* of that person reaches it; there is no way to emit one as `none`.
|
|
101
|
+
*/
|
|
102
|
+
export declare function emitAssistantEvent<K extends AssistantEventType>(ctx: OperationContext, subject: string, event: Omit<DomainEventInput, 'type' | 'payload' | 'schemaVersion' | 'piiClass' | 'subjectId'> & {
|
|
103
|
+
type: K;
|
|
104
|
+
payload: AssistantEvents['events'][K];
|
|
105
|
+
}): void;
|
|
106
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAiB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,uEAAuE;AACvE,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,gDAAgD;AAChD,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,8FAA8F;AAC9F,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC7D;AAED;;;GAGG;AACH,MAAM,WAAW,iCAAiC;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qDAAqD;AACrD,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,uDAAuD;AACvD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE;QACN,0BAA0B,EAAE,6BAA6B,CAAC;QAC1D,wBAAwB,EAAE,2BAA2B,CAAC;QACtD,yBAAyB,EAAE,4BAA4B,CAAC;QACxD,8BAA8B,EAAE,iCAAiC,CAAC;QAClE,6BAA6B,EAAE,gCAAgC,CAAC;QAChE,yBAAyB,EAAE,4BAA4B,CAAC;KACzD,CAAC;CACH,CAAC;AAEF,0CAA0C;AAC1C,MAAM,MAAM,kBAAkB,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,sBAAsB;yCACL,CAAC;uCACH,CAAC;wCACA,CAAC;6CACI,CAAC;4CACF,CAAC;wCACL,CAAC;CACyB,CAAC;AAExD,oGAAoG;AACpG,eAAO,MAAM,yBAAyB,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,EAEpB,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,kBAAkB,EAC7D,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,GAAG,UAAU,GAAG,WAAW,CAAC,GAAG;IAC/F,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;CACvC,GACA,IAAI,CAON"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine-assistant's event contract (#696) — what a VERTICAL imports so that consuming this
|
|
3
|
+
* engine by event is checked rather than guessed.
|
|
4
|
+
*
|
|
5
|
+
* Milestones only. The thread's own record is `assistant_events`; these are what the rest of
|
|
6
|
+
* the platform learns about it. A text delta is not a platform event: forty of them make one
|
|
7
|
+
* answer, and an outbox row each would be the record written twice.
|
|
8
|
+
*
|
|
9
|
+
* **No text and no tool data ride an event.** A message, a tool input and a tool output are
|
|
10
|
+
* the person's data. So a payload names the thread, the call and the tool, and nothing else:
|
|
11
|
+
* not the approval's `preview` wording, which can restate what the person asked for, and not
|
|
12
|
+
* its digest either — a SHA-256 of a small input is recovered by guessing the input, so it
|
|
13
|
+
* is the input. Nor does a payload name the person: who a thread belongs to is the
|
|
14
|
+
* envelope's `subjectId`.
|
|
15
|
+
*
|
|
16
|
+
* **Every milestone is `pseudonymous`, keyed to the thread's subject** (`emitAssistantEvent`
|
|
17
|
+
* makes that the only way to emit one). A thread id names one person's conversation, so the
|
|
18
|
+
* outbox rows that carry it are that person's; with a `subjectId`, an erasure (K-46) redacts
|
|
19
|
+
* their payloads along with the thread's own rows.
|
|
20
|
+
*
|
|
21
|
+
* VERTICAL-FACING ONLY. A sibling engine consuming one of these must NOT import it (R1,
|
|
22
|
+
* star topology).
|
|
23
|
+
*
|
|
24
|
+
* Additive, like every other engine surface: a new event type may appear, an existing
|
|
25
|
+
* payload field does not change shape without a `schemaVersion` bump.
|
|
26
|
+
*/
|
|
27
|
+
import { dataSubjectId } from '@substrat-run/contracts';
|
|
28
|
+
/**
|
|
29
|
+
* Every event type this engine emits, and the `schemaVersion` each is emitted at — the ONE
|
|
30
|
+
* home for that number (#1597). `emitAssistantEvent` stamps it and the manifest's `emits` is
|
|
31
|
+
* derived from it, so the two cannot drift. Bumping a version is K-39's REPLACE.
|
|
32
|
+
*/
|
|
33
|
+
export const assistantEventVersions = {
|
|
34
|
+
'assistant.thread-created': 1,
|
|
35
|
+
'assistant.turn-started': 1,
|
|
36
|
+
'assistant.turn-finished': 1,
|
|
37
|
+
'assistant.approval-requested': 1,
|
|
38
|
+
'assistant.approval-resolved': 1,
|
|
39
|
+
'assistant.thread-closed': 1,
|
|
40
|
+
};
|
|
41
|
+
/** The manifest's `events.emits`, read off {@link assistantEventVersions} — never hand-declared. */
|
|
42
|
+
export const assistantEmitDeclarations = Object.entries(assistantEventVersions).map(([type, schemaVersion]) => ({ type, schemaVersion }));
|
|
43
|
+
/**
|
|
44
|
+
* `ctx.emit`, with the event type and its payload welded together, so the map above is the
|
|
45
|
+
* source rather than a description nothing holds in agreement. It stamps `schemaVersion`, and
|
|
46
|
+
* makes every milestone `pseudonymous` under `subject` — the thread's owner — so an erasure
|
|
47
|
+
* of that person reaches it; there is no way to emit one as `none`.
|
|
48
|
+
*/
|
|
49
|
+
export function emitAssistantEvent(ctx, subject, event) {
|
|
50
|
+
ctx.emit({
|
|
51
|
+
...event,
|
|
52
|
+
schemaVersion: assistantEventVersions[event.type],
|
|
53
|
+
piiClass: 'pseudonymous',
|
|
54
|
+
subjectId: dataSubjectId.parse(subject),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,aAAa,EAAyB,MAAM,yBAAyB,CAAC;AA4D/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,0BAA0B,EAAE,CAAC;IAC7B,wBAAwB,EAAE,CAAC;IAC3B,yBAAyB,EAAE,CAAC;IAC5B,8BAA8B,EAAE,CAAC;IACjC,6BAA6B,EAAE,CAAC;IAChC,yBAAyB,EAAE,CAAC;CACyB,CAAC;AAExD,oGAAoG;AACpG,MAAM,CAAC,MAAM,yBAAyB,GAA8C,MAAM,CAAC,OAAO,CAChG,sBAAsB,CACvB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,GAAqB,EACrB,OAAe,EACf,KAGC;IAED,GAAG,CAAC,IAAI,CAAC;QACP,GAAG,KAAK;QACR,aAAa,EAAE,sBAAsB,CAAC,KAAK,CAAC,IAAI,CAAC;QACjD,QAAQ,EAAE,cAAc;QACxB,SAAS,EAAE,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC;KACxC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HOST code, separate from the in-scope engine. Bind `caller` to the authenticated
|
|
3
|
+
* person and `storage` to the assistant host's system principal, in the SAME scope.
|
|
4
|
+
* Never expose `storage` itself to a browser or model tool.
|
|
5
|
+
*/
|
|
6
|
+
import { type AttachmentRecord } from '@substrat-run/contracts';
|
|
7
|
+
import type { OpenedAttachment, ScopeAttachments, ScopeStub } from '@substrat-run/kernel';
|
|
8
|
+
export declare const ASSISTANT_FILE_MAX_BYTES: number;
|
|
9
|
+
export interface AssistantFileUpload {
|
|
10
|
+
threadId: string;
|
|
11
|
+
filename: string;
|
|
12
|
+
body: Uint8Array;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A host mounts these methods on its own authenticated routes. Bytes bypass invoke;
|
|
16
|
+
* ownership and audit checks go through the engine's real operation handlers.
|
|
17
|
+
* There is intentionally no automatic import: the host's tools preview a diff and
|
|
18
|
+
* use the existing approval/claim flow before changing domain entities.
|
|
19
|
+
*/
|
|
20
|
+
export declare function assistantFiles(caller: ScopeStub, storage: ScopeAttachments): {
|
|
21
|
+
upload(input: AssistantFileUpload): Promise<AttachmentRecord>;
|
|
22
|
+
list: (threadId: string) => Promise<AttachmentRecord[]>;
|
|
23
|
+
open(threadId: string, attachmentId: string): Promise<OpenedAttachment>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Trusted host lifecycle hook: remove blobs BEFORE erasing a thread's owner. The
|
|
27
|
+
* kernel's subject erasure deletes engine rows, but does not shred attachment blobs.
|
|
28
|
+
* Repeatable; failed blob deletions follow the platform's orphan-sweep policy.
|
|
29
|
+
* Do not expose as an unauthenticated route.
|
|
30
|
+
*/
|
|
31
|
+
export declare function purgeAssistantThreadFiles(storage: ScopeAttachments, threadId: string): Promise<void>;
|
|
32
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAqC,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACnG,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAG1F,eAAO,MAAM,wBAAwB,QAAmB,CAAC;AAGzD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;CAClB;AAYD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB;kBASnD,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC;qBANvC,MAAM,KAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;mBAkB3C,MAAM,gBAAgB,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;EAShF;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1G"}
|