@adecore/agents 0.40.1 → 0.40.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/async.d.ts +3 -0
- package/dist/async.js +10 -0
- package/dist/chat/chat-core.js +2 -3
- package/dist/client-sinks.d.ts +8 -0
- package/dist/client-sinks.js +23 -0
- package/dist/fs.d.ts +1 -0
- package/dist/fs.js +11 -1
- package/dist/host/index.d.ts +1 -1
- package/dist/host/index.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/single-flight.d.ts +6 -0
- package/dist/single-flight.js +27 -0
- package/package.json +3 -3
- package/src/async.ts +17 -0
- package/src/chat/chat-core.ts +2 -3
- package/src/client-sinks.ts +29 -0
- package/src/fs.ts +12 -1
- package/src/host/index.ts +1 -1
- package/src/index.ts +3 -2
- package/src/single-flight.ts +33 -0
package/dist/async.d.ts
CHANGED
|
@@ -5,3 +5,6 @@ export interface DeadlineTimer {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function withDeadline<T>(promise: Promise<T>, ms: number, message: string, timer?: DeadlineTimer): Promise<T>;
|
|
7
7
|
export declare function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T>;
|
|
8
|
+
export type Timer = (ms: number, run: () => void) => () => void;
|
|
9
|
+
export declare const cancellableTimeout: Timer;
|
|
10
|
+
export declare const unrefTimeout: Timer;
|
package/dist/async.js
CHANGED
|
@@ -32,3 +32,13 @@ export function withDeadline(promise, ms, message, timer = SYSTEM_TIMER) {
|
|
|
32
32
|
export function withTimeout(promise, ms, message) {
|
|
33
33
|
return withDeadline(promise, ms, message);
|
|
34
34
|
}
|
|
35
|
+
export const cancellableTimeout = (ms, run) => {
|
|
36
|
+
const timer = setTimeout(run, ms);
|
|
37
|
+
return () => clearTimeout(timer);
|
|
38
|
+
};
|
|
39
|
+
/* The same, without holding the process open for it. */
|
|
40
|
+
export const unrefTimeout = (ms, run) => {
|
|
41
|
+
const timer = setTimeout(run, ms);
|
|
42
|
+
timer.unref?.();
|
|
43
|
+
return () => clearTimeout(timer);
|
|
44
|
+
};
|
package/dist/chat/chat-core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { ChatAttachmentUploadsSchema, generatedImageAttachment, HOST_TASK_ORIGIN } from '@adecore/agent-contracts';
|
|
2
|
+
import { ChatAttachmentUploadsSchema, generatedImageAttachment, holdsConversation, HOST_TASK_ORIGIN } from '@adecore/agent-contracts';
|
|
3
3
|
import { ClientSinks } from '../client-sinks.js';
|
|
4
4
|
import { errorText } from '../error-text.js';
|
|
5
5
|
import { AccountError, definedEnv, isDefaultAccountOf, launchEnv, storedAccount } from '../providers/accounts/launch.js';
|
|
@@ -371,8 +371,7 @@ export class ChatCore {
|
|
|
371
371
|
}
|
|
372
372
|
// As named, so a switch to the default account is refused while it is turned off.
|
|
373
373
|
this.requireAccount(kind, requested);
|
|
374
|
-
|
|
375
|
-
if (spoke && !this.canContinue(kind, from, to)) {
|
|
374
|
+
if (holdsConversation(session.info) && !this.canContinue(kind, from, to)) {
|
|
376
375
|
const label = this.accountLabel(kind, to);
|
|
377
376
|
throw new AccountError('account-incompatible', `This conversation is kept by the account '${this.accountLabel(kind, from)}', which '${label}' cannot read. Fork the chat to go on under '${label}'.`);
|
|
378
377
|
}
|
package/dist/client-sinks.d.ts
CHANGED
|
@@ -7,3 +7,11 @@ export declare class ClientSinks<TEvent> {
|
|
|
7
7
|
to(clientId: string, event: TEvent): void;
|
|
8
8
|
clientIds(): string[];
|
|
9
9
|
}
|
|
10
|
+
export declare class WatchedSinks<TEvent> {
|
|
11
|
+
private readonly sinks;
|
|
12
|
+
private readonly watching;
|
|
13
|
+
subscribe(clientId: string, sink: (event: TEvent) => void): () => void;
|
|
14
|
+
watch(clientId: string): void;
|
|
15
|
+
unwatch(clientId: string): void;
|
|
16
|
+
emit(event: TEvent): void;
|
|
17
|
+
}
|
package/dist/client-sinks.js
CHANGED
|
@@ -32,3 +32,26 @@ export class ClientSinks {
|
|
|
32
32
|
return [...this.sinks.keys()];
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/* The clients of a part that hear its events only after they asked for them with `<area>.watch`. */
|
|
36
|
+
export class WatchedSinks {
|
|
37
|
+
sinks = new ClientSinks();
|
|
38
|
+
watching = new Set();
|
|
39
|
+
subscribe(clientId, sink) {
|
|
40
|
+
const unsubscribe = this.sinks.subscribe(clientId, sink);
|
|
41
|
+
return () => {
|
|
42
|
+
this.watching.delete(clientId);
|
|
43
|
+
unsubscribe();
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
watch(clientId) {
|
|
47
|
+
this.watching.add(clientId);
|
|
48
|
+
}
|
|
49
|
+
unwatch(clientId) {
|
|
50
|
+
this.watching.delete(clientId);
|
|
51
|
+
}
|
|
52
|
+
emit(event) {
|
|
53
|
+
for (const clientId of this.watching) {
|
|
54
|
+
this.sinks.to(clientId, event);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/fs.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export declare function removeFile(path: string, remove?: (path: string, options
|
|
|
11
11
|
force: true;
|
|
12
12
|
}) => Promise<void>): Promise<void>;
|
|
13
13
|
export declare function fileExists(path: string): Promise<boolean>;
|
|
14
|
+
export declare function entryExists(path: string): Promise<boolean>;
|
package/dist/fs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { renameSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { open, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { lstat, open, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
import { wait } from './async.js';
|
|
5
5
|
// Windows can refuse a rename while a watcher or an indexer holds the target; a few short retries cover it.
|
|
@@ -120,3 +120,13 @@ export async function fileExists(path) {
|
|
|
120
120
|
return false;
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
/* Whether anything is at the path, a link that leads nowhere included, where `fileExists` follows the link. */
|
|
124
|
+
export async function entryExists(path) {
|
|
125
|
+
try {
|
|
126
|
+
await lstat(path);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
package/dist/host/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export { RUIMTE_SESSION_VARIABLES, cliEnvironment, type CliEnvironmentPolicy } f
|
|
|
3
3
|
export { type AgentHandler, type AgentHandlers, chatHandlers, accountHandlers, usageHandlers, type AgentRequestMap, type ChatRequestType } from './handlers.ts';
|
|
4
4
|
export { memoryPortPair } from './memory-port.ts';
|
|
5
5
|
export { type AgentWiring, wireAgents, type AgentWiringOptions } from './wiring.ts';
|
|
6
|
-
export { ClientSinks } from '../client-sinks.ts';
|
|
6
|
+
export { ClientSinks, WatchedSinks } from '../client-sinks.ts';
|
|
7
7
|
export type { AgentEvent, AgentEventMap, AgentSink } from '../events.ts';
|
package/dist/host/index.js
CHANGED
|
@@ -4,4 +4,4 @@ export { RUIMTE_SESSION_VARIABLES, cliEnvironment } from './environment.js';
|
|
|
4
4
|
export { chatHandlers, accountHandlers, usageHandlers } from './handlers.js';
|
|
5
5
|
export { memoryPortPair } from './memory-port.js';
|
|
6
6
|
export { wireAgents } from './wiring.js';
|
|
7
|
-
export { ClientSinks } from '../client-sinks.js';
|
|
7
|
+
export { ClientSinks, WatchedSinks } from '../client-sinks.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export { wait, withTimeout } from './async.ts';
|
|
1
|
+
export { wait, withTimeout, cancellableTimeout, unrefTimeout, type Timer } from './async.ts';
|
|
2
2
|
export { CodedError } from './coded-error.ts';
|
|
3
3
|
export { describeError, setErrorStacks, errorText } from './error-text.ts';
|
|
4
|
-
export { tempNameFor, writeAtomic, replaceSync, writeAtomicSync, isNotFound, fileExists, type WriteAtomicOptions } from './fs.ts';
|
|
4
|
+
export { tempNameFor, writeAtomic, replaceSync, writeAtomicSync, hasErrorCode, isNotFound, fileExists, entryExists, type WriteAtomicOptions } from './fs.ts';
|
|
5
5
|
export { RecordDirectory, type RecordDirectoryOptions } from './record-directory.ts';
|
|
6
6
|
export { runProcess, type ProcessResult, type RunProcessOptions } from './run-process.ts';
|
|
7
7
|
export { Serializer, KeyedSerializer } from './serializer.ts';
|
|
8
|
+
export { SingleFlight } from './single-flight.ts';
|
|
8
9
|
export { cleanTitle, readLines } from './title-file.ts';
|
|
9
10
|
export { type DirectoryWatcher, type WatchSeams, supportsRecursive, SYSTEM_WATCH, type Settled, settled, PerClientWatches, type WatchDirectory, type Schedule } from './watch-seam.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
|
|
2
|
-
export { wait, withTimeout } from './async.js';
|
|
2
|
+
export { wait, withTimeout, cancellableTimeout, unrefTimeout } from './async.js';
|
|
3
3
|
export { CodedError } from './coded-error.js';
|
|
4
4
|
export { describeError, setErrorStacks, errorText } from './error-text.js';
|
|
5
|
-
export { tempNameFor, writeAtomic, replaceSync, writeAtomicSync, isNotFound, fileExists } from './fs.js';
|
|
5
|
+
export { tempNameFor, writeAtomic, replaceSync, writeAtomicSync, hasErrorCode, isNotFound, fileExists, entryExists } from './fs.js';
|
|
6
6
|
export { RecordDirectory } from './record-directory.js';
|
|
7
7
|
export { runProcess } from './run-process.js';
|
|
8
8
|
export { Serializer, KeyedSerializer } from './serializer.js';
|
|
9
|
+
export { SingleFlight } from './single-flight.js';
|
|
9
10
|
export { cleanTitle, readLines } from './title-file.js';
|
|
10
11
|
export { supportsRecursive, SYSTEM_WATCH, settled, PerClientWatches } from './watch-seam.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Work that runs once per key at a time: a caller that asks while it runs shares its outcome, and
|
|
3
|
+
* a key that settled leaves nothing behind.
|
|
4
|
+
*/
|
|
5
|
+
export class SingleFlight {
|
|
6
|
+
flights = new Map();
|
|
7
|
+
/* The run in flight for `key`, if any. */
|
|
8
|
+
get(key) {
|
|
9
|
+
return this.flights.get(key);
|
|
10
|
+
}
|
|
11
|
+
has(key) {
|
|
12
|
+
return this.flights.has(key);
|
|
13
|
+
}
|
|
14
|
+
run(key, work) {
|
|
15
|
+
const running = this.flights.get(key);
|
|
16
|
+
if (running !== undefined) {
|
|
17
|
+
return running;
|
|
18
|
+
}
|
|
19
|
+
const flight = work().finally(() => {
|
|
20
|
+
if (this.flights.get(key) === flight) {
|
|
21
|
+
this.flights.delete(key);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
this.flights.set(key, flight);
|
|
25
|
+
return flight;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.3",
|
|
4
4
|
"description": "Node-compatible agent chat hosts, providers, accounts, usage and durable task coordination.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
"test": "bun --conditions=source test"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
|
-
"@adecore/agent-contracts": "0.40.
|
|
85
|
-
"@adecore/intelligent-ui": "0.40.
|
|
84
|
+
"@adecore/agent-contracts": "0.40.3",
|
|
85
|
+
"@adecore/intelligent-ui": "0.40.3",
|
|
86
86
|
"zod": "^4.6.7"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
package/src/async.ts
CHANGED
|
@@ -50,3 +50,20 @@ export function withDeadline<T>(promise: Promise<T>, ms: number, message: string
|
|
|
50
50
|
export function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {
|
|
51
51
|
return withDeadline(promise, ms, message);
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
/* Runs `run` after `ms` and answers how to call it off. */
|
|
55
|
+
export type Timer = (ms: number, run: () => void) => () => void;
|
|
56
|
+
|
|
57
|
+
export const cancellableTimeout: Timer = (ms, run) => {
|
|
58
|
+
const timer = setTimeout(run, ms);
|
|
59
|
+
|
|
60
|
+
return () => clearTimeout(timer);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/* The same, without holding the process open for it. */
|
|
64
|
+
export const unrefTimeout: Timer = (ms, run) => {
|
|
65
|
+
const timer = setTimeout(run, ms);
|
|
66
|
+
timer.unref?.();
|
|
67
|
+
|
|
68
|
+
return () => clearTimeout(timer);
|
|
69
|
+
};
|
package/src/chat/chat-core.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { join } from 'node:path';
|
|
|
2
2
|
import {
|
|
3
3
|
ChatAttachmentUploadsSchema,
|
|
4
4
|
generatedImageAttachment,
|
|
5
|
+
holdsConversation,
|
|
5
6
|
HOST_TASK_ORIGIN,
|
|
6
7
|
type AgentKind,
|
|
7
8
|
type ChatAttachment,
|
|
@@ -612,9 +613,7 @@ export class ChatCore {
|
|
|
612
613
|
|
|
613
614
|
// As named, so a switch to the default account is refused while it is turned off.
|
|
614
615
|
this.requireAccount(kind, requested);
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
if (spoke && !this.canContinue(kind, from, to)) {
|
|
616
|
+
if (holdsConversation(session.info) && !this.canContinue(kind, from, to)) {
|
|
618
617
|
const label = this.accountLabel(kind, to);
|
|
619
618
|
|
|
620
619
|
throw new AccountError(
|
package/src/client-sinks.ts
CHANGED
|
@@ -39,3 +39,32 @@ export class ClientSinks<TEvent> {
|
|
|
39
39
|
return [...this.sinks.keys()];
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
/* The clients of a part that hear its events only after they asked for them with `<area>.watch`. */
|
|
44
|
+
export class WatchedSinks<TEvent> {
|
|
45
|
+
private readonly sinks = new ClientSinks<TEvent>();
|
|
46
|
+
private readonly watching = new Set<string>();
|
|
47
|
+
|
|
48
|
+
subscribe(clientId: string, sink: (event: TEvent) => void): () => void {
|
|
49
|
+
const unsubscribe = this.sinks.subscribe(clientId, sink);
|
|
50
|
+
|
|
51
|
+
return () => {
|
|
52
|
+
this.watching.delete(clientId);
|
|
53
|
+
unsubscribe();
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
watch(clientId: string): void {
|
|
58
|
+
this.watching.add(clientId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
unwatch(clientId: string): void {
|
|
62
|
+
this.watching.delete(clientId);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
emit(event: TEvent): void {
|
|
66
|
+
for (const clientId of this.watching) {
|
|
67
|
+
this.sinks.to(clientId, event);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/fs.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { renameSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { open, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { lstat, open, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
import { wait } from './async.ts';
|
|
5
5
|
|
|
@@ -143,3 +143,14 @@ export async function fileExists(path: string): Promise<boolean> {
|
|
|
143
143
|
return false;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
+
|
|
147
|
+
/* Whether anything is at the path, a link that leads nowhere included, where `fileExists` follows the link. */
|
|
148
|
+
export async function entryExists(path: string): Promise<boolean> {
|
|
149
|
+
try {
|
|
150
|
+
await lstat(path);
|
|
151
|
+
|
|
152
|
+
return true;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/host/index.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { RUIMTE_SESSION_VARIABLES, cliEnvironment, type CliEnvironmentPolicy } f
|
|
|
4
4
|
export { type AgentHandler, type AgentHandlers, chatHandlers, accountHandlers, usageHandlers, type AgentRequestMap, type ChatRequestType } from './handlers.ts';
|
|
5
5
|
export { memoryPortPair } from './memory-port.ts';
|
|
6
6
|
export { type AgentWiring, wireAgents, type AgentWiringOptions } from './wiring.ts';
|
|
7
|
-
export { ClientSinks } from '../client-sinks.ts';
|
|
7
|
+
export { ClientSinks, WatchedSinks } from '../client-sinks.ts';
|
|
8
8
|
export type { AgentEvent, AgentEventMap, AgentSink } from '../events.ts';
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
|
|
2
|
-
export { wait, withTimeout } from './async.ts';
|
|
2
|
+
export { wait, withTimeout, cancellableTimeout, unrefTimeout, type Timer } from './async.ts';
|
|
3
3
|
export { CodedError } from './coded-error.ts';
|
|
4
4
|
export { describeError, setErrorStacks, errorText } from './error-text.ts';
|
|
5
|
-
export { tempNameFor, writeAtomic, replaceSync, writeAtomicSync, isNotFound, fileExists, type WriteAtomicOptions } from './fs.ts';
|
|
5
|
+
export { tempNameFor, writeAtomic, replaceSync, writeAtomicSync, hasErrorCode, isNotFound, fileExists, entryExists, type WriteAtomicOptions } from './fs.ts';
|
|
6
6
|
export { RecordDirectory, type RecordDirectoryOptions } from './record-directory.ts';
|
|
7
7
|
export { runProcess, type ProcessResult, type RunProcessOptions } from './run-process.ts';
|
|
8
8
|
export { Serializer, KeyedSerializer } from './serializer.ts';
|
|
9
|
+
export { SingleFlight } from './single-flight.ts';
|
|
9
10
|
export { cleanTitle, readLines } from './title-file.ts';
|
|
10
11
|
|
|
11
12
|
export {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Work that runs once per key at a time: a caller that asks while it runs shares its outcome, and
|
|
3
|
+
* a key that settled leaves nothing behind.
|
|
4
|
+
*/
|
|
5
|
+
export class SingleFlight<T> {
|
|
6
|
+
private readonly flights = new Map<string, Promise<T>>();
|
|
7
|
+
|
|
8
|
+
/* The run in flight for `key`, if any. */
|
|
9
|
+
get(key: string): Promise<T> | undefined {
|
|
10
|
+
return this.flights.get(key);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
has(key: string): boolean {
|
|
14
|
+
return this.flights.has(key);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
run(key: string, work: () => Promise<T>): Promise<T> {
|
|
18
|
+
const running = this.flights.get(key);
|
|
19
|
+
|
|
20
|
+
if (running !== undefined) {
|
|
21
|
+
return running;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const flight = work().finally(() => {
|
|
25
|
+
if (this.flights.get(key) === flight) {
|
|
26
|
+
this.flights.delete(key);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
this.flights.set(key, flight);
|
|
30
|
+
|
|
31
|
+
return flight;
|
|
32
|
+
}
|
|
33
|
+
}
|