@schlenkr/ragents 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/apps/web/dist/help/guide-distributed.html +22 -8
- package/apps/web/dist/help/guide-distributed.md +28 -7
- package/apps/web/dist/host-web.json +1 -1
- package/apps/web/src/server-client.ts +165 -0
- package/bundles/ragents.activity/ragents-bundle.json +1 -1
- package/bundles/ragents.actor-programs/ragents-bundle.json +1 -1
- package/bundles/ragents.ask/ragents-bundle.json +1 -1
- package/bundles/ragents.browser/ragents-bundle.json +1 -1
- package/bundles/ragents.documents/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-fsharp/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-roslyn/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-typescript/ragents-bundle.json +1 -1
- package/bundles/ragents.model-relay/ragents-bundle.json +1 -1
- package/bundles/ragents.orchestration/ragents-bundle.json +1 -1
- package/bundles/ragents.overseer/ragents-bundle.json +1 -1
- package/bundles/ragents.processes/ragents-bundle.json +1 -1
- package/bundles/ragents.product/ragents-bundle.json +1 -1
- package/bundles/ragents.profile-distribution/ragents-bundle.json +1 -1
- package/bundles/ragents.reference/ragents-bundle.json +1 -1
- package/bundles/ragents.todo/ragents-bundle.json +1 -1
- package/bundles/ragents.transcript/ragents-bundle.json +1 -1
- package/bundles/ragents.watch/ragents-bundle.json +1 -1
- package/bundles/ragents.workspace/ragents-bundle.json +1 -1
- package/package.json +11 -2
- package/packages/ai/dist/api/openai-compatible-response.d.ts +2 -0
- package/packages/ai/package.json +1 -0
- package/packages/ai/src/api/ai-sdk-transport.ts +4 -2
- package/packages/ai/src/api/openai-compatible-response.ts +60 -0
- package/packages/workspace-executor/src/bundled-tools.ts +49 -0
- package/packages/workspace-executor/src/owner-lifetime.d.mts +18 -0
- package/packages/workspace-executor/src/owner-lifetime.mjs +102 -0
- package/plugins/ragents.workspace/client/transport.ts +9 -11
- package/plugins/ragents.workspace/client/workspace-client.ts +8 -1
- package/scripts/package/ragents.mjs +30 -4
- package/scripts/workspace-client/run-workspace-client.ts +51 -34
- package/scripts/package/build-package.ts +0 -319
- package/scripts/package/publish-package.ts +0 -144
package/README.md
CHANGED
|
@@ -41,7 +41,11 @@ npm install -g @schlenkr/ragents
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
- `workspace-client <server-url> [folder ...]` - register this machine and its folders as a
|
|
44
|
-
workspace for a server, so a run on that server works in these folders.
|
|
44
|
+
workspace for a server, so a run on that server works in these folders. Includes the same
|
|
45
|
+
ripgrep and Windows Bash as the VS Code extension. `RAGENTS_USER` and `RAGENTS_PASSWORD` in
|
|
46
|
+
the environment enable session renewal; `RAGENTS_TOKEN` supplies an existing or personal token.
|
|
47
|
+
Terminal or parent closure stops it and its children. Use `--detached` explicitly for nohup
|
|
48
|
+
or a service manager; SIGINT/SIGTERM still stop it.
|
|
45
49
|
|
|
46
50
|
```sh
|
|
47
51
|
ragents workspace-client https://ragents.example.com ~/projects/demo
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
<a class="nav-github" href="https://github.com/SchlenkR/RAgents" aria-label="GitHub"><svg class="gh-icon" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12 2a10 10 0 0 0-3.2 19.5c.5.1.7-.2.7-.5v-1.7c-2.8.6-3.4-1.3-3.4-1.3-.5-1.2-1.1-1.5-1.1-1.5-.9-.6.1-.6.1-.6 1 .1 1.5 1 1.5 1 .9 1.6 2.4 1.1 3 .9.1-.7.4-1.1.6-1.4-2.2-.3-4.6-1.1-4.6-5 0-1.1.4-2 1-2.7-.1-.3-.4-1.3.1-2.7 0 0 .8-.3 2.8 1a9.6 9.6 0 0 1 5 0c1.9-1.3 2.8-1 2.8-1 .5 1.4.2 2.4.1 2.7.6.7 1 1.6 1 2.7 0 3.9-2.4 4.7-4.6 5 .4.3.7.9.7 1.9V21c0 .3.2.6.7.5A10 10 0 0 0 12 2Z"/></svg><span class="gh-label">GitHub</span></a>
|
|
131
131
|
</nav>
|
|
132
132
|
<button class="nav-menu" type="button" aria-expanded="false" aria-controls="main-navigation"><span data-menu-label>Menu</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
|
|
133
|
-
</header><main class="shell" id="main"><div class="guide-layout"><aside class="guide-sidebar"><nav class="guide-chapters" aria-label="Guide navigation"><a href="guide.html">Overview</a><div class="guide-nav-group"><p>Understand</p><a href="guide-ideas.html">Core ideas</a><a href="guide-runtime.html">Actors and messages</a></div><div class="guide-nav-group"><p>Use</p><a href="guide-getting-started.html">Get started</a><a href="guide-clients.html">Web and VS Code</a><a href="guide-distributed.html" aria-current="page">Distributed work</a></div><div class="guide-nav-group"><p>Build</p><a href="guide-functions.html">Work with TypeScript</a><a href="guide-programs.html">Build mini-apps</a><a href="guide-plugins.html">Plugins and skills</a></div><div class="guide-nav-group"><p>Access</p><a href="guide-access.html">Assign permissions</a></div></nav></aside><details class="guide-mobile-nav"><summary>Guide: Distributed work</summary><nav class="guide-chapters" aria-label="Guide navigation"><a href="guide.html">Overview</a><div class="guide-nav-group"><p>Understand</p><a href="guide-ideas.html">Core ideas</a><a href="guide-runtime.html">Actors and messages</a></div><div class="guide-nav-group"><p>Use</p><a href="guide-getting-started.html">Get started</a><a href="guide-clients.html">Web and VS Code</a><a href="guide-distributed.html" aria-current="page">Distributed work</a></div><div class="guide-nav-group"><p>Build</p><a href="guide-functions.html">Work with TypeScript</a><a href="guide-programs.html">Build mini-apps</a><a href="guide-plugins.html">Plugins and skills</a></div><div class="guide-nav-group"><p>Access</p><a href="guide-access.html">Assign permissions</a></div></nav></details><article class="guide-body"><header class="guide-title"><p class="eyebrow"><a href="guide.html">Guide</a></p><h1>Distributed work</h1><p>Run the host locally or remotely, keep tools beside the project, and move runs between servers.</p></header><details class="guide-page-nav"><summary>On this page</summary><nav aria-label="On this page"><a href="#work-without-a-checkout">Work without a checkout</a><a href="#connect-to-a-server">Connect to a server</a><a href="#transfer-a-run">Transfer a run</a><a href="#work-on-windows">Work on Windows</a></nav></details><h2 id="work-without-a-checkout">Work without a checkout</h2>
|
|
133
|
+
</header><main class="shell" id="main"><div class="guide-layout"><aside class="guide-sidebar"><nav class="guide-chapters" aria-label="Guide navigation"><a href="guide.html">Overview</a><div class="guide-nav-group"><p>Understand</p><a href="guide-ideas.html">Core ideas</a><a href="guide-runtime.html">Actors and messages</a></div><div class="guide-nav-group"><p>Use</p><a href="guide-getting-started.html">Get started</a><a href="guide-clients.html">Web and VS Code</a><a href="guide-distributed.html" aria-current="page">Distributed work</a></div><div class="guide-nav-group"><p>Build</p><a href="guide-functions.html">Work with TypeScript</a><a href="guide-programs.html">Build mini-apps</a><a href="guide-plugins.html">Plugins and skills</a></div><div class="guide-nav-group"><p>Access</p><a href="guide-access.html">Assign permissions</a></div></nav></aside><details class="guide-mobile-nav"><summary>Guide: Distributed work</summary><nav class="guide-chapters" aria-label="Guide navigation"><a href="guide.html">Overview</a><div class="guide-nav-group"><p>Understand</p><a href="guide-ideas.html">Core ideas</a><a href="guide-runtime.html">Actors and messages</a></div><div class="guide-nav-group"><p>Use</p><a href="guide-getting-started.html">Get started</a><a href="guide-clients.html">Web and VS Code</a><a href="guide-distributed.html" aria-current="page">Distributed work</a></div><div class="guide-nav-group"><p>Build</p><a href="guide-functions.html">Work with TypeScript</a><a href="guide-programs.html">Build mini-apps</a><a href="guide-plugins.html">Plugins and skills</a></div><div class="guide-nav-group"><p>Access</p><a href="guide-access.html">Assign permissions</a></div></nav></details><article class="guide-body"><header class="guide-title"><p class="eyebrow"><a href="guide.html">Guide</a></p><h1>Distributed work</h1><p>Run the host locally or remotely, keep tools beside the project, and move runs between servers.</p></header><details class="guide-page-nav"><summary>On this page</summary><nav aria-label="On this page"><a href="#work-without-a-checkout">Work without a checkout</a><a href="#connect-to-a-server">Connect to a server</a><a href="#run-a-cli-workstation">Run a CLI workstation</a><a href="#transfer-a-run">Transfer a run</a><a href="#work-on-windows">Work on Windows</a></nav></details><h2 id="work-without-a-checkout">Work without a checkout</h2>
|
|
134
134
|
<p>Using RAgents does not require a repository. <code>pnpm build:package</code> creates the npm package
|
|
135
135
|
<code>@schlenkr/ragents</code> from this checkout in <code>dist/ragents</code>; with <code>--pack</code>, it also creates
|
|
136
136
|
<code>dist/schlenkr-ragents-<version>.tgz</code>. The package contains the server, engine, executor, all
|
|
@@ -246,6 +246,22 @@ records only forwarded calls with user, alias, target, status, and token count.<
|
|
|
246
246
|
starts with the new web interface as long as the host API stays the same. Otherwise the start
|
|
247
247
|
refuses the fetched bundles, and <code>connect</code> names the version to install.</li>
|
|
248
248
|
</ul>
|
|
249
|
+
<h2 id="run-a-cli-workstation">Run a CLI workstation</h2>
|
|
250
|
+
<p><code>ragents workspace-client <server-url> [folders ...]</code> registers this machine with the same tools
|
|
251
|
+
and executor as the VS Code extension. No folder means the current directory; <code>--id</code> and <code>--label</code>
|
|
252
|
+
set its stable identity and display name. The npm installation includes ripgrep on every supported
|
|
253
|
+
platform and the curated Bash on Windows. Plugin tools are provisioned before registration.</p>
|
|
254
|
+
<p>Set <code>RAGENTS_TOKEN</code> for a personal or existing session token. For automatic sign-in and renewal,
|
|
255
|
+
configure <code>RAGENTS_USER</code> and <code>RAGENTS_PASSWORD</code> in the process environment. Neither is a command-line
|
|
256
|
+
option. Invalid or absent credentials when sign-in is required produce a visible failure and a
|
|
257
|
+
nonzero exit. A temporary connection loss reconnects and registers the workstation again.</p>
|
|
258
|
+
<p>Closing the terminal or SSH session, closing stdin, or ending the owning process unregisters the
|
|
259
|
+
workstation and stops its children. SIGINT, SIGTERM, and SIGHUP also stop it. For an intentional
|
|
260
|
+
background service, pass <code>--detached</code> and let a service manager own it, or run:</p>
|
|
261
|
+
<pre><code class="language-sh">nohup ragents workspace-client https://ragents.example.com /home/user/project --detached >workspace.log 2>&1 </dev/null &
|
|
262
|
+
</code></pre>
|
|
263
|
+
<p>The flag ignores terminal closure, parent loss, and SIGHUP; it does not daemonize. SIGINT and
|
|
264
|
+
SIGTERM still unregister and shut down. Use the service manager's stop command to end a service.</p>
|
|
249
265
|
<h2 id="transfer-a-run">Transfer a run</h2>
|
|
250
266
|
<p>A run can move from one server to another and continue there, for example from a notebook to an
|
|
251
267
|
always-on machine or from a central server to a developer for local inspection.</p>
|
|
@@ -299,13 +315,11 @@ otherwise it tells the model to exclude dependency and build folders from <code>
|
|
|
299
315
|
contains no git, so your login works as usual: Git Credential Manager, <code>~/.gitconfig</code> and
|
|
300
316
|
<code>~/.ssh</code>. On your own machine the bash inherits your whole environment, except the variables of
|
|
301
317
|
VS Code itself and <code>BASH_ENV</code>/<code>ENV</code>.</li>
|
|
302
|
-
<li><strong>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<code
|
|
307
|
-
<code>PATH</code> is used if there is one. A <code>RAGENTS_RG</code> that points to a missing file stops the server
|
|
308
|
-
start or the workstation's registration.</li>
|
|
318
|
+
<li><strong>CLI workstation</strong> (<code>ragents workspace-client</code>) carries the same Bash and ripgrep as the
|
|
319
|
+
extension. npm installs only the optional tools package for this machine; keep optional
|
|
320
|
+
dependencies enabled. A checkout uses the extension build from <code>pnpm bundle:rg</code> and
|
|
321
|
+
<code>pnpm bundle:bash</code>. A standalone server (<code>ragents start</code>) still takes <code>RAGENTS_BASH</code> and
|
|
322
|
+
<code>RAGENTS_RG</code> from its environment; these variables do not override workstation bundles.</li>
|
|
309
323
|
<li><strong>Node.js</strong> is enough with <code>@schlenkr/ragents</code>. A checkout additionally needs <strong>pnpm</strong>, and
|
|
310
324
|
<code>pnpm install</code>, <code>pnpm build:agent</code>, and <code>scripts/start.sh</code> need a bash of your own, such as
|
|
311
325
|
Git Bash.</li>
|
|
@@ -143,6 +143,29 @@ expose only the alias.
|
|
|
143
143
|
starts with the new web interface as long as the host API stays the same. Otherwise the start
|
|
144
144
|
refuses the fetched bundles, and `connect` names the version to install.
|
|
145
145
|
|
|
146
|
+
## Run a CLI workstation
|
|
147
|
+
|
|
148
|
+
`ragents workspace-client <server-url> [folders ...]` registers this machine with the same tools
|
|
149
|
+
and executor as the VS Code extension. No folder means the current directory; `--id` and `--label`
|
|
150
|
+
set its stable identity and display name. The npm installation includes ripgrep on every supported
|
|
151
|
+
platform and the curated Bash on Windows. Plugin tools are provisioned before registration.
|
|
152
|
+
|
|
153
|
+
Set `RAGENTS_TOKEN` for a personal or existing session token. For automatic sign-in and renewal,
|
|
154
|
+
configure `RAGENTS_USER` and `RAGENTS_PASSWORD` in the process environment. Neither is a command-line
|
|
155
|
+
option. Invalid or absent credentials when sign-in is required produce a visible failure and a
|
|
156
|
+
nonzero exit. A temporary connection loss reconnects and registers the workstation again.
|
|
157
|
+
|
|
158
|
+
Closing the terminal or SSH session, closing stdin, or ending the owning process unregisters the
|
|
159
|
+
workstation and stops its children. SIGINT, SIGTERM, and SIGHUP also stop it. For an intentional
|
|
160
|
+
background service, pass `--detached` and let a service manager own it, or run:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
nohup ragents workspace-client https://ragents.example.com /home/user/project --detached >workspace.log 2>&1 </dev/null &
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The flag ignores terminal closure, parent loss, and SIGHUP; it does not daemonize. SIGINT and
|
|
167
|
+
SIGTERM still unregister and shut down. Use the service manager's stop command to end a service.
|
|
168
|
+
|
|
146
169
|
## Transfer a run
|
|
147
170
|
|
|
148
171
|
A run can move from one server to another and continue there, for example from a notebook to an
|
|
@@ -203,13 +226,11 @@ The local host and workspace also run on Windows. Requirements are:
|
|
|
203
226
|
contains no git, so your login works as usual: Git Credential Manager, `~/.gitconfig` and
|
|
204
227
|
`~/.ssh`. On your own machine the bash inherits your whole environment, except the variables of
|
|
205
228
|
VS Code itself and `BASH_ENV`/`ENV`.
|
|
206
|
-
- **
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
`PATH` is used if there is one. A `RAGENTS_RG` that points to a missing file stops the server
|
|
212
|
-
start or the workstation's registration.
|
|
229
|
+
- **CLI workstation** (`ragents workspace-client`) carries the same Bash and ripgrep as the
|
|
230
|
+
extension. npm installs only the optional tools package for this machine; keep optional
|
|
231
|
+
dependencies enabled. A checkout uses the extension build from `pnpm bundle:rg` and
|
|
232
|
+
`pnpm bundle:bash`. A standalone server (`ragents start`) still takes `RAGENTS_BASH` and
|
|
233
|
+
`RAGENTS_RG` from its environment; these variables do not override workstation bundles.
|
|
213
234
|
- **Node.js** is enough with `@schlenkr/ragents`. A checkout additionally needs **pnpm**, and
|
|
214
235
|
`pnpm install`, `pnpm build:agent`, and `scripts/start.sh` need a bash of your own, such as
|
|
215
236
|
Git Bash.
|
|
@@ -138,6 +138,6 @@
|
|
|
138
138
|
"packages/ragents/src/rpc/peer.ts": "9050dff218bdc6603478a77bdca241629f55c03b65f6efe7df598b53446b4c09",
|
|
139
139
|
"packages/ragents/src/rpc/protocol.ts": "82bbcb8181a2f90708dc607922fef4a9d472cd82d6dede09324df778773699a6",
|
|
140
140
|
"packages/ragents/src/runtime/domain-error.ts": "d6bf8fa09769c4137e616c83a84cb9c05a58bfa6c2e32c9404a1c202815837a4",
|
|
141
|
-
"pnpm-lock.yaml": "
|
|
141
|
+
"pnpm-lock.yaml": "2428bb94f1bb44305e258747adef0db465a9dcceacc8eb901d082e3acfd06b2f"
|
|
142
142
|
}
|
|
143
143
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { AccessSnapshot } from "../../../packages/ragents/src/access";
|
|
2
|
+
import { accessSnapshotFrom } from "./access-session";
|
|
3
|
+
import { RpcClient } from "./rpc/client";
|
|
4
|
+
|
|
5
|
+
export class ServerError extends Error {
|
|
6
|
+
constructor(message: string, readonly status: number, readonly code?: string) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ServerError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class UnreachableError extends Error {
|
|
13
|
+
constructor(message: string) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "UnreachableError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const sessionTokenFrom = (setCookie: readonly string[]): string => {
|
|
20
|
+
for (const cookie of setCookie) {
|
|
21
|
+
const pair = cookie.split(";")[0] ?? "";
|
|
22
|
+
const token = pair.slice(pair.indexOf("=") + 1).trim();
|
|
23
|
+
if (/^[A-Za-z0-9_-]{43}$/.test(token)) return token;
|
|
24
|
+
}
|
|
25
|
+
throw new ServerError("The server did not deliver a session token.", 500);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const errorFrom = async (response: Response): Promise<ServerError> => {
|
|
29
|
+
let message = `The server responded with ${response.status}.`;
|
|
30
|
+
let code: string | undefined;
|
|
31
|
+
try {
|
|
32
|
+
const body = await response.json() as { error?: unknown; message?: unknown; code?: unknown };
|
|
33
|
+
if (typeof body.error === "string" && body.error) message = body.error;
|
|
34
|
+
else if (typeof body.message === "string" && body.message) message = body.message;
|
|
35
|
+
if (typeof body.code === "string") code = body.code;
|
|
36
|
+
} catch {}
|
|
37
|
+
return new ServerError(message, response.status, code);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** A workstation's access: sign-in and delivery over HTTP, everything else as JSON-RPC; the session token goes as bearer. */
|
|
41
|
+
export interface SignInOptions {
|
|
42
|
+
readonly credentials: () => Promise<{ id: string; password: string } | undefined>;
|
|
43
|
+
readonly onToken?: (token: string) => Promise<void>;
|
|
44
|
+
readonly log?: (line: string) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ServerClient {
|
|
48
|
+
#authenticating: Promise<boolean> | undefined;
|
|
49
|
+
#signInFailure: string | undefined;
|
|
50
|
+
readonly origin: string;
|
|
51
|
+
readonly rpc: RpcClient;
|
|
52
|
+
|
|
53
|
+
constructor(readonly baseUrl: string, private token: string | undefined, private readonly request: typeof fetch = fetch, private readonly signIn?: SignInOptions) {
|
|
54
|
+
this.origin = new URL(baseUrl).origin;
|
|
55
|
+
this.rpc = new RpcClient({
|
|
56
|
+
baseUrl: this.origin,
|
|
57
|
+
fetch: (input, init) => this.#authorized(input, init),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
signInFailure(): string | undefined {
|
|
62
|
+
return this.#signInFailure;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async #authorized(input: Parameters<typeof fetch>[0], init?: RequestInit): Promise<Response> {
|
|
66
|
+
const token = this.token;
|
|
67
|
+
const send = () => this.request(input, { ...init, headers: this.headers(init?.headers as Record<string, string> | undefined ?? {}) });
|
|
68
|
+
const response = await send();
|
|
69
|
+
if (response.status !== 401 || !this.signIn || init?.signal?.aborted) return response;
|
|
70
|
+
const rejection = await response.clone().json().catch(() => undefined) as { code?: unknown } | undefined;
|
|
71
|
+
if (rejection?.code !== "login-required") {
|
|
72
|
+
this.#signInFailure = "Access token required: this server does not accept automatic user/password sign-in. Supply a valid access token.";
|
|
73
|
+
return response;
|
|
74
|
+
}
|
|
75
|
+
if (token === this.token) {
|
|
76
|
+
this.#authenticating ??= this.#authenticate().finally(() => { this.#authenticating = undefined; });
|
|
77
|
+
if (!await this.#authenticating) return response;
|
|
78
|
+
}
|
|
79
|
+
if (init?.signal?.aborted) return response;
|
|
80
|
+
await response.body?.cancel();
|
|
81
|
+
const retried = await send();
|
|
82
|
+
if (retried.status === 401) this.#signInFailure = "Sign-in required: the server rejected the renewed session.";
|
|
83
|
+
return retried;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async #authenticate(): Promise<boolean> {
|
|
87
|
+
try {
|
|
88
|
+
const credentials = await this.signIn!.credentials();
|
|
89
|
+
if (!credentials) {
|
|
90
|
+
this.#signInFailure = "Sign-in required: no credentials are configured for session renewal.";
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
this.signIn!.log?.(`== Signing in to ${this.origin} with configured credentials`);
|
|
94
|
+
const { token } = await this.login(credentials.id, credentials.password);
|
|
95
|
+
await this.signIn!.onToken?.(token);
|
|
96
|
+
this.useToken(token);
|
|
97
|
+
this.#signInFailure = undefined;
|
|
98
|
+
return true;
|
|
99
|
+
} catch {
|
|
100
|
+
this.#signInFailure = "Sign-in required: automatic sign-in failed. Check the configured credentials and server availability.";
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get hasToken(): boolean {
|
|
106
|
+
return this.token !== undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get accessToken(): string | undefined {
|
|
110
|
+
return this.token;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The client reads the token on every request, so the messaging layer stays in place. */
|
|
114
|
+
useToken(token: string | undefined): void {
|
|
115
|
+
this.token = token;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
url(path: string): string {
|
|
119
|
+
return new URL(path, `${this.origin}/`).toString();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
headers(extra: Record<string, string> = {}): Record<string, string> {
|
|
123
|
+
return this.token === undefined ? extra : { ...extra, authorization: `Bearer ${this.token}` };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async fetch(path: string, init: RequestInit = {}): Promise<Response> {
|
|
127
|
+
try {
|
|
128
|
+
return await this.#authorized(this.url(path), init);
|
|
129
|
+
} catch (cause) {
|
|
130
|
+
throw new UnreachableError(`${this.origin} is unreachable: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async json<T>(path: string, init: RequestInit = {}, validate: (value: unknown) => T = (value) => value as T): Promise<T> {
|
|
135
|
+
const response = await this.fetch(path, init);
|
|
136
|
+
if (!response.ok) throw await errorFrom(response);
|
|
137
|
+
return validate(await response.json());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async access(): Promise<AccessSnapshot> {
|
|
141
|
+
const snapshot = await this.json("/api/access", { cache: "no-store" }, accessSnapshotFrom);
|
|
142
|
+
if (!snapshot.enabled || snapshot.user || !this.signIn) return snapshot;
|
|
143
|
+
this.#authenticating ??= this.#authenticate().finally(() => { this.#authenticating = undefined; });
|
|
144
|
+
return await this.#authenticating ? this.json("/api/access", { cache: "no-store" }, accessSnapshotFrom) : snapshot;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async login(id: string, password: string): Promise<{ snapshot: AccessSnapshot; token: string }> {
|
|
148
|
+
const response = await this.request(this.url("/api/access/login"), {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: { "content-type": "application/json" },
|
|
151
|
+
body: JSON.stringify({ id, password }),
|
|
152
|
+
signal: AbortSignal.timeout(10_000),
|
|
153
|
+
redirect: "error",
|
|
154
|
+
});
|
|
155
|
+
if (!response.ok) throw await errorFrom(response);
|
|
156
|
+
const snapshot = accessSnapshotFrom(await response.json());
|
|
157
|
+
if (!snapshot.user) throw new ServerError("The server did not confirm the sign-in.", 500);
|
|
158
|
+
return { snapshot, token: sessionTokenFrom(response.headers.getSetCookie()) };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async logout(): Promise<void> {
|
|
162
|
+
const response = await this.fetch("/api/access/logout", { method: "POST" });
|
|
163
|
+
if (!response.ok && response.status !== 401) throw await errorFrom(response);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"revision": "b7b04d050f5f4bcb0980880167b8e2cdd3ad533ba07f589dd0de471b9e55c0fd",
|
|
26
|
-
"sourceRevision": "
|
|
26
|
+
"sourceRevision": "51444903dfadf6f23f55ad0369575949aec7fd1696b5f34450834d06a3fd23ed",
|
|
27
27
|
"server": "server/index.js",
|
|
28
28
|
"web": {
|
|
29
29
|
"entry": "web/index.js",
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
}
|
|
196
196
|
},
|
|
197
197
|
"revision": "15fc5afb9aac414335069c28989cae4fa95fec8a2937584f8497a42ce01ffaba",
|
|
198
|
-
"sourceRevision": "
|
|
198
|
+
"sourceRevision": "1e6b4725db96bd433b77a1f09f751a41152f77d2d394736bc51886b32ccdd691",
|
|
199
199
|
"server": "server/index.js",
|
|
200
200
|
"web": {
|
|
201
201
|
"entry": "web/index.js",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
"revision": "0c115b8cade07b18dee1b1d11200bbc0427d98d13d0315d9bd47c8619ded8fbc",
|
|
75
|
-
"sourceRevision": "
|
|
75
|
+
"sourceRevision": "ce0c0ff38394586fe032f02838bd9fcf644642c31f4a08f1575ca2aaade7bfbf",
|
|
76
76
|
"server": "server/index.js",
|
|
77
77
|
"web": {
|
|
78
78
|
"entry": "web/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"web": {}
|
|
33
33
|
},
|
|
34
34
|
"revision": "333c6a9b57576a4ab95556e201da67ed864acdf02787ce5edbd653e7beba6020",
|
|
35
|
-
"sourceRevision": "
|
|
35
|
+
"sourceRevision": "43dc6b7d6064ff67e5bbd6ab2a32ee77bca8e562a2c1566fb223ec2f7d5c6792",
|
|
36
36
|
"server": "server/index.js",
|
|
37
37
|
"executor": "executor/index.mjs",
|
|
38
38
|
"exports": {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
103
|
"revision": "3fdba8c39f30027176229901cf731b778170f0e1597c287ad94f7e48a430b5e8",
|
|
104
|
-
"sourceRevision": "
|
|
104
|
+
"sourceRevision": "4ec5929328998f6fff1cb2d4dd5a97d8d2352b1b858e4a88706728eafe2fead1",
|
|
105
105
|
"server": "server/index.js",
|
|
106
106
|
"web": {
|
|
107
107
|
"entry": "web/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"revision": "a39dafc264c6703d9481f358b21e5bf3997dc5d2dc7fe0d969d4905d49c7a1f8",
|
|
32
|
-
"sourceRevision": "
|
|
32
|
+
"sourceRevision": "0c1724bed58160a4bfb446c45ef22ea4fa063a43b358d7c78ef64b220b36f653",
|
|
33
33
|
"server": "server/index.js",
|
|
34
34
|
"web": {
|
|
35
35
|
"entry": "web/index.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
"revision": "cbe81aa88a4ea56476537fabbbb0995d09ccbfa269dd6a003df50a57c8286b02",
|
|
44
|
-
"sourceRevision": "
|
|
44
|
+
"sourceRevision": "f351e54c82571eb6cde2b5e75379e9d6f13e8f57b38bd36d661e68b53d2e355d",
|
|
45
45
|
"server": "server/index.js",
|
|
46
46
|
"web": {
|
|
47
47
|
"entry": "web/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"revision": "8387de707a4fe8fdb8c67315346522b331728f8adf51a7fadda8db662681f0c4",
|
|
21
|
-
"sourceRevision": "
|
|
21
|
+
"sourceRevision": "b188ca64f6ebf562df726715a505837a537cf9a432d549dc3dbec205ed8ff5ab",
|
|
22
22
|
"server": "server/index.js",
|
|
23
23
|
"web": {
|
|
24
24
|
"entry": "web/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"web": {}
|
|
27
27
|
},
|
|
28
28
|
"revision": "9090692eca49235944f149b25dccdc986a3c638007d8da2fb35f74d942454379",
|
|
29
|
-
"sourceRevision": "
|
|
29
|
+
"sourceRevision": "f57ee897c518cd93bc1a089c0e37ed0154b04da83549ecedcc3c340ab1e36c62",
|
|
30
30
|
"server": "server/index.js",
|
|
31
31
|
"exports": {
|
|
32
32
|
"server": {},
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
}
|
|
174
174
|
},
|
|
175
175
|
"revision": "cbd72b88af0c8ac76486b4c0617545fc53b23315cf93dd924631ceb8e389b34b",
|
|
176
|
-
"sourceRevision": "
|
|
176
|
+
"sourceRevision": "2a6a34d768f487b027fbffac38beed2afb04c8bbec3311d8f05ce7cd5d90ef4c",
|
|
177
177
|
"server": "server/index.js",
|
|
178
178
|
"web": {
|
|
179
179
|
"entry": "web/index.js",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
}
|
|
131
131
|
},
|
|
132
132
|
"revision": "58b8b02a346c4323afdef69cfd96b8f3717806919f2c46f146bcc803ef12bac4",
|
|
133
|
-
"sourceRevision": "
|
|
133
|
+
"sourceRevision": "23351003ee6534e2535ca08493c457c7366047bd1b4de4ef267fcac618bdef37",
|
|
134
134
|
"server": "server/index.js",
|
|
135
135
|
"web": {
|
|
136
136
|
"entry": "web/index.js",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
"revision": "e602a8711818533fc7372c5cb2ca51ae55362079d9944da008db9084621b5ccc",
|
|
81
|
-
"sourceRevision": "
|
|
81
|
+
"sourceRevision": "7913d1ad21363aa3742d7c2b5b32ab7b3be49aeef8d9fc3c9a5caf5df28cab2c",
|
|
82
82
|
"server": "server/index.js",
|
|
83
83
|
"web": {
|
|
84
84
|
"entry": "web/index.js",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
78
|
"revision": "7a3eb7a847ce6a4c8324b9c073c3c7a13c760088a194d419fe21342575c4208f",
|
|
79
|
-
"sourceRevision": "
|
|
79
|
+
"sourceRevision": "9164cdb7e60d692e88452d5f24dbd6bbbcb8e9eafff911e84726d3cad345cb10",
|
|
80
80
|
"server": "server/index.js",
|
|
81
81
|
"web": {
|
|
82
82
|
"entry": "web/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"web": {}
|
|
54
54
|
},
|
|
55
55
|
"revision": "3e99cf5cb8921685ad83aa6aaf9657da4c648f9d68cb744988a73f227c342e1d",
|
|
56
|
-
"sourceRevision": "
|
|
56
|
+
"sourceRevision": "37a64513c41b585ecfef5171e26eac917e7cb69f069e057f0f62bbc3bfbba29e",
|
|
57
57
|
"server": "server/index.js",
|
|
58
58
|
"exports": {
|
|
59
59
|
"server": {},
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"revision": "2c684e9daab9b2ffe4c002c03d4fa8549b2d4dd0b20513fb5eec0e4f10a396ee",
|
|
24
|
-
"sourceRevision": "
|
|
24
|
+
"sourceRevision": "3e5f750bd9f707910743092b94df19a6b9bf9956ad7ab33818b5cd820bb85075",
|
|
25
25
|
"server": "server/index.js",
|
|
26
26
|
"web": {
|
|
27
27
|
"entry": "web/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"revision": "5e18b249f3c8b53b61820ef05da59823d58163c6962e6d836dcb37c05034aad0",
|
|
36
|
-
"sourceRevision": "
|
|
36
|
+
"sourceRevision": "1c5eafe2e0da0c98f0c03b6cdd33b3d3cddc799da393df6a94c6d290fb285cbd",
|
|
37
37
|
"server": "server/index.js",
|
|
38
38
|
"web": {
|
|
39
39
|
"entry": "web/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"web": {}
|
|
24
24
|
},
|
|
25
25
|
"revision": "e3bb513053f6385f9d78d314d28302324095a5163c4e1563df6fc8b3251305f7",
|
|
26
|
-
"sourceRevision": "
|
|
26
|
+
"sourceRevision": "840c4195ce48a06cc8c49513c96a94381e7d8e10802f8835e3aab1c6ece01118",
|
|
27
27
|
"server": "server/index.js",
|
|
28
28
|
"exports": {
|
|
29
29
|
"server": {},
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"web": {}
|
|
26
26
|
},
|
|
27
27
|
"revision": "007ee50ab186e3070930f23e9795210fa36c723365657325d5440cc2f0b81d60",
|
|
28
|
-
"sourceRevision": "
|
|
28
|
+
"sourceRevision": "8cec5a0d238ff52a630bd3f822cf95ea9a640626c8c7345def28c4f1feabff10",
|
|
29
29
|
"server": "server/index.js",
|
|
30
30
|
"exports": {
|
|
31
31
|
"server": {
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
}
|
|
138
138
|
},
|
|
139
139
|
"revision": "db9370b7b3449c44be49986b074806351b857c430963aa878e209d2d5c9ba4f8",
|
|
140
|
-
"sourceRevision": "
|
|
140
|
+
"sourceRevision": "25a05a48b591f8f4b575b06d495f7f191037d7419b79a98ad321328c455f4e0d",
|
|
141
141
|
"server": "server/index.js",
|
|
142
142
|
"web": {
|
|
143
143
|
"entry": "web/index.js",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schlenkr/ragents",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "RAgents host: server, engine, plugins and tools without a source checkout",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"dompurify": "3.4.13",
|
|
51
51
|
"elkjs": "0.12.0",
|
|
52
52
|
"esbuild": "0.28.2",
|
|
53
|
+
"eventsource-parser": "3.1.1",
|
|
53
54
|
"handlebars": "4.7.9",
|
|
54
55
|
"highlight.js": "11.12.0",
|
|
55
56
|
"lucide-react": "1.46.0",
|
|
@@ -69,7 +70,15 @@
|
|
|
69
70
|
"vscode-jsonrpc": "9.0.2",
|
|
70
71
|
"yaml": "2.9.0"
|
|
71
72
|
},
|
|
73
|
+
"optionalDependencies": {
|
|
74
|
+
"@schlenkr/ragents-tools-win32-x64": "0.1.18",
|
|
75
|
+
"@schlenkr/ragents-tools-win32-arm64": "0.1.18",
|
|
76
|
+
"@schlenkr/ragents-tools-darwin-x64": "0.1.18",
|
|
77
|
+
"@schlenkr/ragents-tools-darwin-arm64": "0.1.18",
|
|
78
|
+
"@schlenkr/ragents-tools-linux-x64": "0.1.18",
|
|
79
|
+
"@schlenkr/ragents-tools-linux-arm64": "0.1.18"
|
|
80
|
+
},
|
|
72
81
|
"ragents": {
|
|
73
|
-
"hostVersion": "
|
|
82
|
+
"hostVersion": "6f896323c8bdb2622d4dc9459a901949bf5fae33"
|
|
74
83
|
}
|
|
75
84
|
}
|
package/packages/ai/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
2
2
|
import type { Model, ProviderHeaders } from "../types.ts";
|
|
3
3
|
import { headersToRecord } from "../utils/headers.ts";
|
|
4
|
+
import { normalizeOpenAiResponse } from "./openai-compatible-response.ts";
|
|
4
5
|
|
|
5
6
|
interface SdkOptions<TModel> {
|
|
6
7
|
apiKey?: string;
|
|
@@ -43,9 +44,10 @@ export function createSdkProvider<TModel extends Model<string>>(model: TModel, o
|
|
|
43
44
|
});
|
|
44
45
|
try {
|
|
45
46
|
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
46
|
-
return
|
|
47
|
+
return model.baseUrl && new URL(model.baseUrl).hostname !== "openrouter.ai"
|
|
48
|
+
? await normalizeOpenAiResponse(response) : response;
|
|
47
49
|
} catch (error) {
|
|
48
|
-
await response.body?.cancel();
|
|
50
|
+
if (!response.body?.locked) await response.body?.cancel();
|
|
49
51
|
throw error;
|
|
50
52
|
}
|
|
51
53
|
},
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { EventSourceParserStream, type EventSourceMessage } from "eventsource-parser/stream";
|
|
2
|
+
|
|
3
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
4
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function omitNulls(value: Record<string, unknown>, fields: readonly string[]) {
|
|
8
|
+
for (const field of fields) if (value[field] === null) delete value[field];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeCompletion(value: unknown): unknown {
|
|
12
|
+
if (!isObject(value) || !Array.isArray(value.choices)) return value;
|
|
13
|
+
for (const choice of value.choices) {
|
|
14
|
+
if (!isObject(choice)) continue;
|
|
15
|
+
for (const kind of ["delta", "message"] as const) {
|
|
16
|
+
const message = choice[kind];
|
|
17
|
+
if (!isObject(message)) continue;
|
|
18
|
+
omitNulls(message, [
|
|
19
|
+
"content", "reasoning", "reasoning_content", "reasoning_text", "reasoning_details",
|
|
20
|
+
"tool_calls", "refusal", "audio", "function_call", "images", "annotations",
|
|
21
|
+
]);
|
|
22
|
+
if (kind === "delta") omitNulls(message, ["role"]);
|
|
23
|
+
if (kind === "message" && message.reasoning === undefined && typeof message.reasoning_content === "string") {
|
|
24
|
+
message.reasoning = message.reasoning_content;
|
|
25
|
+
}
|
|
26
|
+
if (!Array.isArray(message.tool_calls)) continue;
|
|
27
|
+
for (const tool of message.tool_calls) {
|
|
28
|
+
if (!isObject(tool)) continue;
|
|
29
|
+
if (tool.type == null && isObject(tool.function)) tool.type = "function";
|
|
30
|
+
if (isObject(tool.function)) omitNulls(tool.function, ["arguments"]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function normalizeOpenAiResponse(response: Response): Promise<Response> {
|
|
38
|
+
if (!response.ok || !response.body) return response;
|
|
39
|
+
const contentType = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
|
|
40
|
+
const headers = new Headers(response.headers);
|
|
41
|
+
headers.delete("content-length");
|
|
42
|
+
headers.delete("content-encoding");
|
|
43
|
+
const init = { status: response.status, statusText: response.statusText, headers };
|
|
44
|
+
if (contentType === "application/json") {
|
|
45
|
+
return new Response(JSON.stringify(normalizeCompletion(await response.json())), init);
|
|
46
|
+
}
|
|
47
|
+
if (contentType !== "text/event-stream") return response;
|
|
48
|
+
const body = response.body
|
|
49
|
+
.pipeThrough(new TextDecoderStream())
|
|
50
|
+
.pipeThrough(new EventSourceParserStream())
|
|
51
|
+
.pipeThrough(new TransformStream<EventSourceMessage, string>({
|
|
52
|
+
transform(event, controller) {
|
|
53
|
+
const data = event.data.trim() === "[DONE]" ? "[DONE]" : JSON.stringify(normalizeCompletion(JSON.parse(event.data)));
|
|
54
|
+
const metadata = `${event.id === undefined ? "" : `id: ${event.id}\n`}${event.event === undefined ? "" : `event: ${event.event}\n`}`;
|
|
55
|
+
controller.enqueue(`${metadata}data: ${data}\n\n`);
|
|
56
|
+
},
|
|
57
|
+
}))
|
|
58
|
+
.pipeThrough(new TextEncoderStream());
|
|
59
|
+
return new Response(body, init);
|
|
60
|
+
}
|