@sympoies/dsh-telegram 0.0.0-bootstrap.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -2
- package/NOTICE +19 -0
- package/README.md +589 -2
- package/cordis.patch.yml +14 -0
- package/lib/access.d.ts +76 -0
- package/lib/access.d.ts.map +1 -0
- package/lib/access.js +137 -0
- package/lib/access.js.map +1 -0
- package/lib/client.js +835 -0
- package/lib/commands.d.ts +51 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +83 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +125 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +97 -0
- package/lib/config.js.map +1 -0
- package/lib/diagnostics.d.ts +58 -0
- package/lib/diagnostics.d.ts.map +1 -0
- package/lib/diagnostics.js +59 -0
- package/lib/diagnostics.js.map +1 -0
- package/lib/failures.d.ts +71 -0
- package/lib/failures.d.ts.map +1 -0
- package/lib/failures.js +139 -0
- package/lib/failures.js.map +1 -0
- package/lib/harness/host.d.ts +106 -0
- package/lib/harness/host.d.ts.map +1 -0
- package/lib/harness/host.js +142 -0
- package/lib/harness/host.js.map +1 -0
- package/lib/harness/message.d.ts +45 -0
- package/lib/harness/message.d.ts.map +1 -0
- package/lib/harness/message.js +28 -0
- package/lib/harness/message.js.map +1 -0
- package/lib/harness/model-selection.d.ts +82 -0
- package/lib/harness/model-selection.d.ts.map +1 -0
- package/lib/harness/model-selection.js +103 -0
- package/lib/harness/model-selection.js.map +1 -0
- package/lib/harness/questions-seam.d.ts +34 -0
- package/lib/harness/questions-seam.d.ts.map +1 -0
- package/lib/harness/questions-seam.js +45 -0
- package/lib/harness/questions-seam.js.map +1 -0
- package/lib/harness/types.d.ts +108 -0
- package/lib/harness/types.d.ts.map +1 -0
- package/lib/harness/types.js +23 -0
- package/lib/harness/types.js.map +1 -0
- package/lib/index.d.ts +65 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +977 -0
- package/lib/index.js.map +1 -0
- package/lib/interact/approvals.d.ts +59 -0
- package/lib/interact/approvals.d.ts.map +1 -0
- package/lib/interact/approvals.js +132 -0
- package/lib/interact/approvals.js.map +1 -0
- package/lib/interact/pending.d.ts +56 -0
- package/lib/interact/pending.d.ts.map +1 -0
- package/lib/interact/pending.js +89 -0
- package/lib/interact/pending.js.map +1 -0
- package/lib/interact/questions.d.ts +100 -0
- package/lib/interact/questions.d.ts.map +1 -0
- package/lib/interact/questions.js +255 -0
- package/lib/interact/questions.js.map +1 -0
- package/lib/interact/surface.d.ts +33 -0
- package/lib/interact/surface.d.ts.map +1 -0
- package/lib/interact/surface.js +39 -0
- package/lib/interact/surface.js.map +1 -0
- package/lib/interact/text-capture.d.ts +38 -0
- package/lib/interact/text-capture.d.ts.map +1 -0
- package/lib/interact/text-capture.js +75 -0
- package/lib/interact/text-capture.js.map +1 -0
- package/lib/media/collect.d.ts +157 -0
- package/lib/media/collect.d.ts.map +1 -0
- package/lib/media/collect.js +235 -0
- package/lib/media/collect.js.map +1 -0
- package/lib/media/extractor.d.ts +117 -0
- package/lib/media/extractor.d.ts.map +1 -0
- package/lib/media/extractor.js +207 -0
- package/lib/media/extractor.js.map +1 -0
- package/lib/media/intake.d.ts +57 -0
- package/lib/media/intake.d.ts.map +1 -0
- package/lib/media/intake.js +115 -0
- package/lib/media/intake.js.map +1 -0
- package/lib/media/limits.d.ts +69 -0
- package/lib/media/limits.d.ts.map +1 -0
- package/lib/media/limits.js +97 -0
- package/lib/media/limits.js.map +1 -0
- package/lib/media/ocr.d.ts +66 -0
- package/lib/media/ocr.d.ts.map +1 -0
- package/lib/media/ocr.js +131 -0
- package/lib/media/ocr.js.map +1 -0
- package/lib/media/screenshot.d.ts +57 -0
- package/lib/media/screenshot.d.ts.map +1 -0
- package/lib/media/screenshot.js +101 -0
- package/lib/media/screenshot.js.map +1 -0
- package/lib/media/vision.d.ts +60 -0
- package/lib/media/vision.d.ts.map +1 -0
- package/lib/media/vision.js +74 -0
- package/lib/media/vision.js.map +1 -0
- package/lib/render/clamp.d.ts +34 -0
- package/lib/render/clamp.d.ts.map +1 -0
- package/lib/render/clamp.js +65 -0
- package/lib/render/clamp.js.map +1 -0
- package/lib/render/escape.d.ts +15 -0
- package/lib/render/escape.d.ts.map +1 -0
- package/lib/render/escape.js +17 -0
- package/lib/render/escape.js.map +1 -0
- package/lib/reply/activity.d.ts +31 -0
- package/lib/reply/activity.d.ts.map +1 -0
- package/lib/reply/activity.js +88 -0
- package/lib/reply/activity.js.map +1 -0
- package/lib/reply/rich-stream.d.ts +173 -0
- package/lib/reply/rich-stream.d.ts.map +1 -0
- package/lib/reply/rich-stream.js +286 -0
- package/lib/reply/rich-stream.js.map +1 -0
- package/lib/reply/split-markdown.d.ts +21 -0
- package/lib/reply/split-markdown.d.ts.map +1 -0
- package/lib/reply/split-markdown.js +87 -0
- package/lib/reply/split-markdown.js.map +1 -0
- package/lib/reply/turn-bridge.d.ts +143 -0
- package/lib/reply/turn-bridge.d.ts.map +1 -0
- package/lib/reply/turn-bridge.js +182 -0
- package/lib/reply/turn-bridge.js.map +1 -0
- package/lib/router.d.ts +415 -0
- package/lib/router.d.ts.map +1 -0
- package/lib/router.js +663 -0
- package/lib/router.js.map +1 -0
- package/lib/secrets.d.ts +30 -0
- package/lib/secrets.d.ts.map +1 -0
- package/lib/secrets.js +51 -0
- package/lib/secrets.js.map +1 -0
- package/lib/session/bindings.d.ts +81 -0
- package/lib/session/bindings.d.ts.map +1 -0
- package/lib/session/bindings.js +170 -0
- package/lib/session/bindings.js.map +1 -0
- package/lib/session/history.d.ts +50 -0
- package/lib/session/history.d.ts.map +1 -0
- package/lib/session/history.js +127 -0
- package/lib/session/history.js.map +1 -0
- package/lib/session/models.d.ts +99 -0
- package/lib/session/models.d.ts.map +1 -0
- package/lib/session/models.js +123 -0
- package/lib/session/models.js.map +1 -0
- package/lib/session/permission.d.ts +83 -0
- package/lib/session/permission.d.ts.map +1 -0
- package/lib/session/permission.js +107 -0
- package/lib/session/permission.js.map +1 -0
- package/lib/session/picker.d.ts +49 -0
- package/lib/session/picker.d.ts.map +1 -0
- package/lib/session/picker.js +107 -0
- package/lib/session/picker.js.map +1 -0
- package/lib/session/preferences.d.ts +55 -0
- package/lib/session/preferences.d.ts.map +1 -0
- package/lib/session/preferences.js +102 -0
- package/lib/session/preferences.js.map +1 -0
- package/lib/session/recovery.d.ts +66 -0
- package/lib/session/recovery.d.ts.map +1 -0
- package/lib/session/recovery.js +112 -0
- package/lib/session/recovery.js.map +1 -0
- package/lib/session/runner.d.ts +223 -0
- package/lib/session/runner.d.ts.map +1 -0
- package/lib/session/runner.js +260 -0
- package/lib/session/runner.js.map +1 -0
- package/lib/session/workspaces.d.ts +28 -0
- package/lib/session/workspaces.d.ts.map +1 -0
- package/lib/session/workspaces.js +53 -0
- package/lib/session/workspaces.js.map +1 -0
- package/lib/telegram/addressing.d.ts +39 -0
- package/lib/telegram/addressing.d.ts.map +1 -0
- package/lib/telegram/addressing.js +81 -0
- package/lib/telegram/addressing.js.map +1 -0
- package/lib/telegram/albums.d.ts +46 -0
- package/lib/telegram/albums.d.ts.map +1 -0
- package/lib/telegram/albums.js +104 -0
- package/lib/telegram/albums.js.map +1 -0
- package/lib/telegram/api.d.ts +238 -0
- package/lib/telegram/api.d.ts.map +1 -0
- package/lib/telegram/api.js +518 -0
- package/lib/telegram/api.js.map +1 -0
- package/lib/telegram/poller.d.ts +59 -0
- package/lib/telegram/poller.d.ts.map +1 -0
- package/lib/telegram/poller.js +94 -0
- package/lib/telegram/poller.js.map +1 -0
- package/lib/telegram/types.d.ts +130 -0
- package/lib/telegram/types.d.ts.map +1 -0
- package/lib/telegram/types.js +9 -0
- package/lib/telegram/types.js.map +1 -0
- package/lib/telegram/typing.d.ts +47 -0
- package/lib/telegram/typing.d.ts.map +1 -0
- package/lib/telegram/typing.js +91 -0
- package/lib/telegram/typing.js.map +1 -0
- package/lib/versions.d.ts +63 -0
- package/lib/versions.d.ts.map +1 -0
- package/lib/versions.js +135 -0
- package/lib/versions.js.map +1 -0
- package/package.json +94 -10
package/lib/failures.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeping the last things that went wrong where somebody can see them.
|
|
3
|
+
*
|
|
4
|
+
* `ctx.logger` reaches whatever sink the deployment composed, and several
|
|
5
|
+
* profiles compose none. A plugin that only logs its failures is then silent
|
|
6
|
+
* about them: the status file says whether the connection is up, and nothing
|
|
7
|
+
* says that a screenshot failed, an attachment was refused, or a turn threw.
|
|
8
|
+
*
|
|
9
|
+
* That is not a hypothetical. Every fault found in this plugin so far was
|
|
10
|
+
* found by someone noticing odd behaviour in a chat and asking about it, not
|
|
11
|
+
* by reading a log — because there was no log to read.
|
|
12
|
+
*
|
|
13
|
+
* So warnings and errors are also kept here: a small ring in memory for
|
|
14
|
+
* `/diag`, and the same ring on disk for when the bot itself is too broken to
|
|
15
|
+
* answer a command.
|
|
16
|
+
*/
|
|
17
|
+
import { mkdir, rename, writeFile } from 'node:fs/promises';
|
|
18
|
+
import { dirname } from 'node:path';
|
|
19
|
+
/** How many to keep. Enough to see a pattern, short enough to read on a phone. */
|
|
20
|
+
const KEEP = 20;
|
|
21
|
+
/** How long to batch writes for; a burst of failures is one write, not twenty. */
|
|
22
|
+
const FLUSH_MS = 2000;
|
|
23
|
+
export class FailureLog {
|
|
24
|
+
options;
|
|
25
|
+
entries = [];
|
|
26
|
+
timer;
|
|
27
|
+
writing = Promise.resolve();
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.options = options;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Record one failure.
|
|
33
|
+
*
|
|
34
|
+
* @param level - how bad it was.
|
|
35
|
+
* @param message - what happened, already joined into one line.
|
|
36
|
+
*/
|
|
37
|
+
record(level, message) {
|
|
38
|
+
const clean = (this.options.redact ?? ((text) => text))(message).trim();
|
|
39
|
+
if (clean === '')
|
|
40
|
+
return;
|
|
41
|
+
const at = (this.options.now?.() ?? new Date()).toISOString();
|
|
42
|
+
this.entries = [{ at, level, message: clean }, ...this.entries].slice(0, this.options.keep ?? KEEP);
|
|
43
|
+
this.schedule();
|
|
44
|
+
}
|
|
45
|
+
/** The failures kept, newest first. */
|
|
46
|
+
recent() {
|
|
47
|
+
return this.entries;
|
|
48
|
+
}
|
|
49
|
+
/** Write now, whatever the batching timer was waiting for. */
|
|
50
|
+
async flush() {
|
|
51
|
+
if (this.timer) {
|
|
52
|
+
clearTimeout(this.timer);
|
|
53
|
+
this.timer = undefined;
|
|
54
|
+
}
|
|
55
|
+
await this.persist();
|
|
56
|
+
}
|
|
57
|
+
/** Stop batching — the plugin is unloading. */
|
|
58
|
+
dispose() {
|
|
59
|
+
if (this.timer)
|
|
60
|
+
clearTimeout(this.timer);
|
|
61
|
+
this.timer = undefined;
|
|
62
|
+
}
|
|
63
|
+
/** Arm the batching timer, unless one is already armed. */
|
|
64
|
+
schedule() {
|
|
65
|
+
if (this.options.file === undefined || this.timer)
|
|
66
|
+
return;
|
|
67
|
+
this.timer = setTimeout(() => {
|
|
68
|
+
this.timer = undefined;
|
|
69
|
+
void this.persist();
|
|
70
|
+
}, this.options.flushMs ?? FLUSH_MS);
|
|
71
|
+
// Never a reason to hold the process open: this is decoration on the way out.
|
|
72
|
+
this.timer.unref?.();
|
|
73
|
+
}
|
|
74
|
+
/** Mirror the ring to disk, one write at a time. */
|
|
75
|
+
async persist() {
|
|
76
|
+
const file = this.options.file;
|
|
77
|
+
if (file === undefined)
|
|
78
|
+
return;
|
|
79
|
+
const snapshot = [...this.entries];
|
|
80
|
+
this.writing = this.writing.then(async () => {
|
|
81
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
82
|
+
try {
|
|
83
|
+
await mkdir(dirname(file), { recursive: true });
|
|
84
|
+
await writeFile(temporary, `${JSON.stringify(snapshot, undefined, 2)}\n`, 'utf8');
|
|
85
|
+
await rename(temporary, file);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// The whole point of this file is to be readable when things are
|
|
89
|
+
// broken; failing to write it must not become another failure.
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
await this.writing;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A logger that also records what it warns and errors about.
|
|
97
|
+
*
|
|
98
|
+
* Wrapping rather than replacing: whatever sink the deployment composed still
|
|
99
|
+
* gets everything, and this only adds a copy of the parts worth looking back
|
|
100
|
+
* at. Debug and info are deliberately not kept — a ring of twenty filled with
|
|
101
|
+
* routine chatter would push out the one line that mattered.
|
|
102
|
+
*
|
|
103
|
+
* @param base - the harness logger.
|
|
104
|
+
* @param log - the ring to tee into.
|
|
105
|
+
*/
|
|
106
|
+
export function recordingLogger(base, log) {
|
|
107
|
+
const join = (message, rest) => [message, ...rest.map(describe)].filter((part) => part !== '').join(' ');
|
|
108
|
+
return {
|
|
109
|
+
debug: (message, ...rest) => base.debug(message, ...rest),
|
|
110
|
+
info: (message, ...rest) => base.info(message, ...rest),
|
|
111
|
+
warn: (message, ...rest) => {
|
|
112
|
+
log.record('warn', join(message, rest));
|
|
113
|
+
base.warn(message, ...rest);
|
|
114
|
+
},
|
|
115
|
+
error: (message, ...rest) => {
|
|
116
|
+
log.record('error', join(message, rest));
|
|
117
|
+
base.error(message, ...rest);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Say what an extra logger argument was, without dumping an object graph. */
|
|
122
|
+
function describe(value) {
|
|
123
|
+
if (value === undefined || value === null)
|
|
124
|
+
return '';
|
|
125
|
+
if (value instanceof Error)
|
|
126
|
+
return value.message;
|
|
127
|
+
if (typeof value === 'string')
|
|
128
|
+
return value;
|
|
129
|
+
if (typeof value === 'object') {
|
|
130
|
+
try {
|
|
131
|
+
return JSON.stringify(value).slice(0, 200);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return '[unserializable]';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return String(value);
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=failures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failures.js","sourceRoot":"","sources":["../src/failures.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driving harness agents.
|
|
3
|
+
*
|
|
4
|
+
* This is the one module that reaches into `ctx.agents`, so the rest of the
|
|
5
|
+
* plugin can be tested without a harness. It translates between the harness's
|
|
6
|
+
* ownership model and the flat {@link RunningAgent} the session runner wants.
|
|
7
|
+
*
|
|
8
|
+
* The distinction that matters is who may tear an agent down. `ctx.agents.get`
|
|
9
|
+
* returns a bare agent that anyone can observe but nobody can dispose;
|
|
10
|
+
* `create`/`resume` return a handle whose disposer is a capability. So handles
|
|
11
|
+
* are kept for the agents this plugin created, and a bare agent found in the
|
|
12
|
+
* registry is borrowed and left alone — disposing something another owner
|
|
13
|
+
* created would pull the session out from under them.
|
|
14
|
+
*/
|
|
15
|
+
import type { Logger } from './types.js';
|
|
16
|
+
import type { AgentHost } from '../session/runner.js';
|
|
17
|
+
import type { MessageFactory } from './message.js';
|
|
18
|
+
import type { AgentContextLike, MutableSelection } from './model-selection.js';
|
|
19
|
+
/** A bare agent from the registry. */
|
|
20
|
+
interface HarnessAgentLike {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
followup(message: unknown): void;
|
|
23
|
+
cancel(cause: string, options?: {
|
|
24
|
+
keepInbox?: boolean;
|
|
25
|
+
}): void;
|
|
26
|
+
}
|
|
27
|
+
/** An owned agent plus its disposer. */
|
|
28
|
+
interface HarnessAgentHandle {
|
|
29
|
+
readonly agent: HarnessAgentLike;
|
|
30
|
+
dispose(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/** The model route an agent opens its requests on. */
|
|
33
|
+
export interface ModelRoute {
|
|
34
|
+
readonly provider: string;
|
|
35
|
+
readonly model: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The preset roster, narrowed to what one agent's composition needs.
|
|
39
|
+
*
|
|
40
|
+
* A preset is where the tools live. The registries themselves are host-plane,
|
|
41
|
+
* but almost every model-facing row — bash, the editor, grep, skills,
|
|
42
|
+
* subagents, todo, plan mode — is registered into the PRESET's scope layer, so
|
|
43
|
+
* an agent that joins no preset reaches the model with only whatever the host
|
|
44
|
+
* composition registered globally. In this deployment that is the web tools
|
|
45
|
+
* and nothing else.
|
|
46
|
+
*/
|
|
47
|
+
export interface AgentPresetsLike {
|
|
48
|
+
resolve(id?: string): Promise<{
|
|
49
|
+
id: string;
|
|
50
|
+
}>;
|
|
51
|
+
mount(agentCtx: unknown, id?: string): Promise<unknown>;
|
|
52
|
+
}
|
|
53
|
+
/** The `ctx.agents` surface this plugin uses. */
|
|
54
|
+
export interface AgentRegistryLike {
|
|
55
|
+
get(sessionId: string): HarnessAgentLike | undefined;
|
|
56
|
+
create(options: {
|
|
57
|
+
sessionId: string;
|
|
58
|
+
meta?: {
|
|
59
|
+
cwd?: string;
|
|
60
|
+
agentPreset?: string;
|
|
61
|
+
};
|
|
62
|
+
agentOptions?: ModelRoute;
|
|
63
|
+
setup?: (agentCtx: unknown) => void | Promise<void>;
|
|
64
|
+
}): Promise<HarnessAgentHandle>;
|
|
65
|
+
resume(options: {
|
|
66
|
+
resumeSessionId: string;
|
|
67
|
+
agentOptions?: ModelRoute;
|
|
68
|
+
setup?: (agentCtx: unknown) => void | Promise<void>;
|
|
69
|
+
}): Promise<HarnessAgentHandle>;
|
|
70
|
+
}
|
|
71
|
+
/** Construction options. */
|
|
72
|
+
export interface HarnessAgentHostOptions {
|
|
73
|
+
readonly agents: AgentRegistryLike;
|
|
74
|
+
readonly message: MessageFactory;
|
|
75
|
+
/**
|
|
76
|
+
* The deployment's model route, read at creation time rather than captured,
|
|
77
|
+
* so a default changed in Settings reaches the next conversation.
|
|
78
|
+
*
|
|
79
|
+
* An agent created without one has no route to name, and prompt assembly
|
|
80
|
+
* fails the turn on an empty `{{model}}` variable — a failure that surfaces
|
|
81
|
+
* as a broken reply rather than as anything about models.
|
|
82
|
+
*/
|
|
83
|
+
readonly selectModel?: () => ModelRoute | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Installs the mutable model selection into a new agent's scope. Absent
|
|
86
|
+
* leaves every turn on the session's own route.
|
|
87
|
+
*/
|
|
88
|
+
readonly installSelection?: (agentCtx: AgentContextLike, selection: MutableSelection) => void;
|
|
89
|
+
/**
|
|
90
|
+
* The preset roster, where the deployment composes one. Absent is a real
|
|
91
|
+
* deployment shape — the harness's own agent factory handles it too — and
|
|
92
|
+
* means the agent gets whatever the host composition registered globally.
|
|
93
|
+
*/
|
|
94
|
+
readonly presets?: AgentPresetsLike;
|
|
95
|
+
/** Preset to compose Telegram agents from; absent takes the roster default. */
|
|
96
|
+
readonly presetId?: () => string | undefined;
|
|
97
|
+
readonly logger?: Logger;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build an {@link AgentHost} over the live harness registry.
|
|
101
|
+
*
|
|
102
|
+
* @param options - the registry, the message factory, and a logger.
|
|
103
|
+
*/
|
|
104
|
+
export declare function createAgentHost(options: HarnessAgentHostOptions): AgentHost;
|
|
105
|
+
export {};
|
|
106
|
+
//# sourceMappingURL=host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/harness/host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driving harness agents.
|
|
3
|
+
*
|
|
4
|
+
* This is the one module that reaches into `ctx.agents`, so the rest of the
|
|
5
|
+
* plugin can be tested without a harness. It translates between the harness's
|
|
6
|
+
* ownership model and the flat {@link RunningAgent} the session runner wants.
|
|
7
|
+
*
|
|
8
|
+
* The distinction that matters is who may tear an agent down. `ctx.agents.get`
|
|
9
|
+
* returns a bare agent that anyone can observe but nobody can dispose;
|
|
10
|
+
* `create`/`resume` return a handle whose disposer is a capability. So handles
|
|
11
|
+
* are kept for the agents this plugin created, and a bare agent found in the
|
|
12
|
+
* registry is borrowed and left alone — disposing something another owner
|
|
13
|
+
* created would pull the session out from under them.
|
|
14
|
+
*/
|
|
15
|
+
import { sameRoute } from './model-selection.js';
|
|
16
|
+
/**
|
|
17
|
+
* Build an {@link AgentHost} over the live harness registry.
|
|
18
|
+
*
|
|
19
|
+
* @param options - the registry, the message factory, and a logger.
|
|
20
|
+
*/
|
|
21
|
+
export function createAgentHost(options) {
|
|
22
|
+
/** Agents this plugin owns, so it can dispose and re-route exactly those. */
|
|
23
|
+
const owned = new Map();
|
|
24
|
+
const wrap = (agent, sessionId) => ({
|
|
25
|
+
sessionId,
|
|
26
|
+
followup(content) {
|
|
27
|
+
agent.followup(options.message(content));
|
|
28
|
+
},
|
|
29
|
+
useModel(route) {
|
|
30
|
+
const entry = owned.get(sessionId);
|
|
31
|
+
// A borrowed agent carries no selection of ours; its route is its own.
|
|
32
|
+
if (!entry)
|
|
33
|
+
return false;
|
|
34
|
+
if (sameRoute(entry.selection.current, route))
|
|
35
|
+
return true;
|
|
36
|
+
entry.selection.current = route;
|
|
37
|
+
return true;
|
|
38
|
+
},
|
|
39
|
+
cancel(reason) {
|
|
40
|
+
agent.cancel(reason);
|
|
41
|
+
},
|
|
42
|
+
async dispose() {
|
|
43
|
+
const entry = owned.get(sessionId);
|
|
44
|
+
owned.delete(sessionId);
|
|
45
|
+
// Only an owner may dispose. A borrowed agent is simply released.
|
|
46
|
+
if (entry)
|
|
47
|
+
await entry.handle.dispose();
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const adopt = (handle, sessionId, selection) => {
|
|
51
|
+
owned.set(sessionId, { handle, selection });
|
|
52
|
+
return wrap(handle.agent, sessionId);
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Everything one new agent needs composed into it.
|
|
56
|
+
*
|
|
57
|
+
* The preset is resolved here, before the agent exists, so a bad id fails
|
|
58
|
+
* the creation rather than half-composing a session. The mount itself must
|
|
59
|
+
* happen inside `setup`, which is the one place the agent is still
|
|
60
|
+
* unpublished and a rejected composition can roll the whole thing back.
|
|
61
|
+
*/
|
|
62
|
+
const prepareAgent = async () => {
|
|
63
|
+
const selection = { current: undefined };
|
|
64
|
+
const install = options.installSelection;
|
|
65
|
+
let preset;
|
|
66
|
+
if (options.presets) {
|
|
67
|
+
try {
|
|
68
|
+
preset = (await options.presets.resolve(options.presetId?.())).id;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
// A named preset that has gone is not worth failing a message over;
|
|
72
|
+
// the roster's own default still composes a usable agent.
|
|
73
|
+
options.logger?.warn('[dsh-telegram] could not resolve the agent preset', error);
|
|
74
|
+
preset = (await options.presets.resolve().catch(() => undefined))?.id;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// The braces matter. The harness calls `.commit()` on whatever setup
|
|
78
|
+
// returns, so handing back the installer's disposer — as an
|
|
79
|
+
// expression-bodied arrow would — crashes agent creation on a function
|
|
80
|
+
// that has no such method. An async body resolving to undefined is fine.
|
|
81
|
+
//
|
|
82
|
+
// Dropping the disposer is right anyway: the listeners live on the
|
|
83
|
+
// agent's own scope and unwind when the agent does.
|
|
84
|
+
const setup = async (agentCtx) => {
|
|
85
|
+
if (install)
|
|
86
|
+
install(agentCtx, selection);
|
|
87
|
+
if (options.presets)
|
|
88
|
+
await options.presets.mount(agentCtx, preset);
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
selection,
|
|
92
|
+
...(install || options.presets ? { setup } : {}),
|
|
93
|
+
...(preset === undefined ? {} : { preset }),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
live(sessionId) {
|
|
98
|
+
const owner = owned.get(sessionId);
|
|
99
|
+
if (owner)
|
|
100
|
+
return wrap(owner.handle.agent, sessionId);
|
|
101
|
+
const bare = options.agents.get(sessionId);
|
|
102
|
+
return bare ? wrap(bare, sessionId) : undefined;
|
|
103
|
+
},
|
|
104
|
+
async create(sessionId, cwd, forced) {
|
|
105
|
+
// A forced route comes from a caller that knows which model it needs —
|
|
106
|
+
// reading an image — and outranks the deployment's default.
|
|
107
|
+
const route = forced ?? options.selectModel?.();
|
|
108
|
+
const prepared = await prepareAgent();
|
|
109
|
+
const handle = await options.agents.create({
|
|
110
|
+
sessionId,
|
|
111
|
+
// Recorded so a later reader — a cold transcript, the web UI's session
|
|
112
|
+
// list — resolves the same composition this agent runs on.
|
|
113
|
+
meta: { cwd, ...(prepared.preset === undefined ? {} : { agentPreset: prepared.preset }) },
|
|
114
|
+
...(route ? { agentOptions: route } : {}),
|
|
115
|
+
...(prepared.setup ? { setup: prepared.setup } : {}),
|
|
116
|
+
});
|
|
117
|
+
return adopt(handle, sessionId, prepared.selection);
|
|
118
|
+
},
|
|
119
|
+
async resume(sessionId) {
|
|
120
|
+
try {
|
|
121
|
+
// Supplied on resume too, matching the harness's own entry points: a
|
|
122
|
+
// log that already names a selection keeps it, and one that does not
|
|
123
|
+
// — such as a session created before this route existed — is repaired.
|
|
124
|
+
const route = options.selectModel?.();
|
|
125
|
+
const prepared = await prepareAgent();
|
|
126
|
+
const handle = await options.agents.resume({
|
|
127
|
+
resumeSessionId: sessionId,
|
|
128
|
+
...(route ? { agentOptions: route } : {}),
|
|
129
|
+
...(prepared.setup ? { setup: prepared.setup } : {}),
|
|
130
|
+
});
|
|
131
|
+
return adopt(handle, sessionId, prepared.selection);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
// A pruned, moved, or incompatible log is an ordinary outcome here; the
|
|
135
|
+
// runner starts a fresh conversation rather than failing the message.
|
|
136
|
+
options.logger?.debug(`[dsh-telegram] resume of '${sessionId}' failed`, error);
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/harness/host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAwFhD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgC;IAC9D,6EAA6E;IAC7E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAA;IAE3C,MAAM,IAAI,GAAG,CAAC,KAAuB,EAAE,SAAiB,EAAgB,EAAE,CAAC,CAAC;QAC1E,SAAS;QAET,QAAQ,CAAC,OAAO;YACd,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;QAC1C,CAAC;QAED,QAAQ,CAAC,KAAK;YACZ,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAClC,uEAAuE;YACvE,IAAI,CAAC,KAAK;gBAAE,OAAO,KAAK,CAAA;YACxB,IAAI,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAA;YAE1D,KAAK,CAAC,SAAS,CAAC,OAAO,GAAG,KAAK,CAAA;YAC/B,OAAO,IAAI,CAAA;QACb,CAAC;QAED,MAAM,CAAC,MAAc;YACnB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACtB,CAAC;QAED,KAAK,CAAC,OAAO;YACX,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAClC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YACvB,kEAAkE;YAClE,IAAI,KAAK;gBAAE,MAAM,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAA;QACzC,CAAC;KACF,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,CACZ,MAA0B,EAC1B,SAAiB,EACjB,SAA2B,EACb,EAAE;QAChB,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAA;QAC3C,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACtC,CAAC,CAAA;IAED;;;;;;;OAOG;IACH,MAAM,YAAY,GAAG,KAAK,IAAI,EAAE;QAC9B,MAAM,SAAS,GAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;QAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAA;QAExC,IAAI,MAA0B,CAAA;QAC9B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YACnE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,oEAAoE;gBACpE,0DAA0D;gBAC1D,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,mDAAmD,EAAE,KAAK,CAAC,CAAA;gBAChF,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAA;YACvE,CAAC;QACH,CAAC;QAED,qEAAqE;QACrE,4DAA4D;QAC5D,uEAAuE;QACvE,yEAAyE;QACzE,EAAE;QACF,mEAAmE;QACnE,oDAAoD;QACpD,MAAM,KAAK,GAAG,KAAK,EAAE,QAAiB,EAAiB,EAAE;YACvD,IAAI,OAAO;gBAAE,OAAO,CAAC,QAA4B,EAAE,SAAS,CAAC,CAAA;YAC7D,IAAI,OAAO,CAAC,OAAO;gBAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QACpE,CAAC,CAAA;QAED,OAAO;YACL,SAAS;YACT,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;SAC5C,CAAA;IACH,CAAC,CAAA;IAED,OAAO;QACL,IAAI,CAAC,SAAS;YACZ,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAClC,IAAI,KAAK;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;YAErD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAC1C,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACjD,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM;YACjC,uEAAuE;YACvE,4DAA4D;YAC5D,MAAM,KAAK,GAAG,MAAM,IAAI,OAAO,CAAC,WAAW,EAAE,EAAE,CAAA;YAC/C,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAA;YACrC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;gBACzC,SAAS;gBACT,uEAAuE;gBACvE,2DAA2D;gBAC3D,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE;gBACzF,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACrD,CAAC,CAAA;YACF,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAA;QACrD,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,SAAS;YACpB,IAAI,CAAC;gBACH,qEAAqE;gBACrE,qEAAqE;gBACrE,uEAAuE;gBACvE,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,EAAE,CAAA;gBACrC,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAA;gBACrC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;oBACzC,eAAe,EAAE,SAAS;oBAC1B,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACrD,CAAC,CAAA;gBACF,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAA;YACrD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,wEAAwE;gBACxE,sEAAsE;gBACtE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,6BAA6B,SAAS,UAAU,EAAE,KAAK,CAAC,CAAA;gBAC9E,OAAO,SAAS,CAAA;YAClB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Building the user message the agent's inbox accepts.
|
|
3
|
+
*
|
|
4
|
+
* `agent.followup()` takes an identified, frozen `UserMessage`. The harness
|
|
5
|
+
* has its own factory for that — `createUserMessage` in `@deepseek-ai/dsh-llm`
|
|
6
|
+
* — and this once tried to import it, preferring the real thing.
|
|
7
|
+
*
|
|
8
|
+
* It never once succeeded. Under pnpm's isolated layout a plugin resolves only
|
|
9
|
+
* its own declared dependencies, and the harness packages belong to the host,
|
|
10
|
+
* so the import failed with `ERR_MODULE_NOT_FOUND` on every call and this
|
|
11
|
+
* shape was always what got used. Claiming otherwise in a comment made the
|
|
12
|
+
* code harder to reason about, not easier.
|
|
13
|
+
*
|
|
14
|
+
* So the documented shape is the only path, and it is exact: an id, the user
|
|
15
|
+
* role, the content blocks, and a `user` source, deep-frozen before it is
|
|
16
|
+
* handed over.
|
|
17
|
+
*/
|
|
18
|
+
/** One block of model-visible content. */
|
|
19
|
+
export type ContentBlock = {
|
|
20
|
+
readonly type: 'text';
|
|
21
|
+
readonly text: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly type: 'image';
|
|
24
|
+
readonly attachment: unknown;
|
|
25
|
+
};
|
|
26
|
+
/** The message shape `agent.followup()` accepts. */
|
|
27
|
+
export interface UserMessageLike {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly role: 'user';
|
|
30
|
+
readonly content: readonly ContentBlock[];
|
|
31
|
+
readonly source: {
|
|
32
|
+
readonly kind: 'user';
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Builds one user message from content blocks.
|
|
37
|
+
*
|
|
38
|
+
* Blocks rather than a string because a prompt may carry an image: the model
|
|
39
|
+
* sees `{ type: 'image', attachment }` beside the text, which is how a
|
|
40
|
+
* screenshot becomes something it can actually look at.
|
|
41
|
+
*/
|
|
42
|
+
export type MessageFactory = (content: readonly ContentBlock[]) => UserMessageLike;
|
|
43
|
+
/** Build one user message in the shape the agent's inbox accepts. */
|
|
44
|
+
export declare function buildUserMessage(content: readonly ContentBlock[]): UserMessageLike;
|
|
45
|
+
//# sourceMappingURL=message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/harness/message.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAA;AAE5D,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IACzC,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAC3C;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,eAAe,CAAA;AAElF,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,eAAe,CAOlF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Building the user message the agent's inbox accepts.
|
|
3
|
+
*
|
|
4
|
+
* `agent.followup()` takes an identified, frozen `UserMessage`. The harness
|
|
5
|
+
* has its own factory for that — `createUserMessage` in `@deepseek-ai/dsh-llm`
|
|
6
|
+
* — and this once tried to import it, preferring the real thing.
|
|
7
|
+
*
|
|
8
|
+
* It never once succeeded. Under pnpm's isolated layout a plugin resolves only
|
|
9
|
+
* its own declared dependencies, and the harness packages belong to the host,
|
|
10
|
+
* so the import failed with `ERR_MODULE_NOT_FOUND` on every call and this
|
|
11
|
+
* shape was always what got used. Claiming otherwise in a comment made the
|
|
12
|
+
* code harder to reason about, not easier.
|
|
13
|
+
*
|
|
14
|
+
* So the documented shape is the only path, and it is exact: an id, the user
|
|
15
|
+
* role, the content blocks, and a `user` source, deep-frozen before it is
|
|
16
|
+
* handed over.
|
|
17
|
+
*/
|
|
18
|
+
import { randomUUID } from 'node:crypto';
|
|
19
|
+
/** Build one user message in the shape the agent's inbox accepts. */
|
|
20
|
+
export function buildUserMessage(content) {
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
id: randomUUID(),
|
|
23
|
+
role: 'user',
|
|
24
|
+
content: Object.freeze(content.map((block) => Object.freeze({ ...block }))),
|
|
25
|
+
source: Object.freeze({ kind: 'user' }),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/harness/message.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAwBxC,qEAAqE;AACrE,MAAM,UAAU,gBAAgB,CAAC,OAAgC;IAC/D,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,EAAE,EAAE,UAAU,EAAE;QAChB,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;QAC3E,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,CAAC;KACjD,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Changing which model a live agent's next step runs on.
|
|
3
|
+
*
|
|
4
|
+
* The harness holds one mutable selection object per agent, installed while
|
|
5
|
+
* the agent is still unpublished, and reads `current` each time it assembles a
|
|
6
|
+
* prompt. Mutating that object is how the web UI's model picker changes a
|
|
7
|
+
* running session's model, and it is what lets a conversation carrying an
|
|
8
|
+
* image run somewhere its neighbours do not.
|
|
9
|
+
*
|
|
10
|
+
* `current: undefined` means "no override", so the agent falls back to the
|
|
11
|
+
* route it was created with. That is what makes reverting free: there is
|
|
12
|
+
* nothing to restore, only an override to drop.
|
|
13
|
+
*
|
|
14
|
+
* ## Why this is reproduced rather than imported
|
|
15
|
+
*
|
|
16
|
+
* The harness exports `installModelSelection` from `@deepseek-ai/dsh-agent`,
|
|
17
|
+
* and importing it would be the obvious thing to do. It cannot be done: under
|
|
18
|
+
* pnpm's isolated layout a plugin resolves only its own declared dependencies,
|
|
19
|
+
* and the harness packages are the host's, not ours. A dynamic import of them
|
|
20
|
+
* fails with `ERR_MODULE_NOT_FOUND` every time — which is exactly how this
|
|
21
|
+
* feature came to do nothing at all while looking like it worked.
|
|
22
|
+
*
|
|
23
|
+
* What it does is two listener registrations on the agent's own context, so
|
|
24
|
+
* that is what this does. The coupling is to two event names and their
|
|
25
|
+
* payloads; `test/model-selection.test.ts` pins both.
|
|
26
|
+
*/
|
|
27
|
+
/** A provider and model an agent's next step should use. */
|
|
28
|
+
export interface ModelRoute {
|
|
29
|
+
readonly provider: string;
|
|
30
|
+
readonly model: string;
|
|
31
|
+
/** Effort to request; absent clears any inherited one. */
|
|
32
|
+
readonly reasoningEffort?: string;
|
|
33
|
+
}
|
|
34
|
+
/** The object read before each prompt assembly. */
|
|
35
|
+
export interface MutableSelection {
|
|
36
|
+
/** The override in force, or undefined to use the agent's own route. */
|
|
37
|
+
current?: ModelRoute | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The selection the last assembly actually used.
|
|
40
|
+
*
|
|
41
|
+
* Recorded at assembly and read at request time so both halves of one step
|
|
42
|
+
* agree, even if `current` changes in between.
|
|
43
|
+
*/
|
|
44
|
+
assembled?: ModelRoute | undefined;
|
|
45
|
+
}
|
|
46
|
+
/** The slice of an agent's scoped context this needs. */
|
|
47
|
+
export interface AgentContextLike {
|
|
48
|
+
on(event: 'system-prompt/assemble', listener: (assembly: unknown, context: unknown, next: () => Promise<PromptAssembly>) => Promise<PromptAssembly>): () => void;
|
|
49
|
+
on(event: 'agent/request', listener: (payload: unknown, next: () => Promise<ModelRequest>) => Promise<ModelRequest>): () => void;
|
|
50
|
+
}
|
|
51
|
+
/** What a prompt assembly carries that this rewrites. */
|
|
52
|
+
interface PromptAssembly {
|
|
53
|
+
variables?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
/** What a model request carries that this rewrites. */
|
|
56
|
+
interface ModelRequest {
|
|
57
|
+
provider?: string;
|
|
58
|
+
model?: string;
|
|
59
|
+
reasoningEffort?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Make an agent follow a mutable selection.
|
|
63
|
+
*
|
|
64
|
+
* @param agentCtx - the agent's scoped context, during unpublished setup.
|
|
65
|
+
* @param selection - the object whose `current` decides each step's route.
|
|
66
|
+
* @returns a disposer removing both listeners.
|
|
67
|
+
*/
|
|
68
|
+
export declare function installModelSelection(agentCtx: AgentContextLike, selection: MutableSelection): () => void;
|
|
69
|
+
/** Whether two routes name the same model and reasoning effort. */
|
|
70
|
+
export declare function sameRoute(a: ModelRoute | undefined, b: ModelRoute | undefined): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Parse a `provider/model` setting.
|
|
73
|
+
*
|
|
74
|
+
* A model id may itself contain slashes — `openai/gpt-5` on a router-style
|
|
75
|
+
* provider is ordinary — so only the first separator divides them.
|
|
76
|
+
*
|
|
77
|
+
* @param value - the configured string; empty means no vision model.
|
|
78
|
+
* @returns the route, or undefined when unset or malformed.
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseRoute(value: string | undefined): ModelRoute | undefined;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=model-selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-selection.d.ts","sourceRoot":"","sources":["../../src/harness/model-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAClC;AAED,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;IAChC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;CACnC;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,CACA,KAAK,EAAE,wBAAwB,EAC/B,QAAQ,EAAE,CACR,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,KAChC,OAAO,CAAC,cAAc,CAAC,GAC3B,MAAM,IAAI,CAAA;IACb,EAAE,CACA,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,GACvF,MAAM,IAAI,CAAA;CACd;AAED,yDAAyD;AACzD,UAAU,cAAc;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAED,uDAAuD;AACvD,UAAU,YAAY;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,gBAAgB,GAC1B,MAAM,IAAI,CA8CZ;AAED,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,EAAE,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,OAAO,CAOvF;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAW5E"}
|