@gatebolt/agent-redroom 0.1.0 → 0.1.2
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 +10 -6
- package/dist/run.js +23 -10
- package/dist/secrets.js +4 -3
- package/dist/sidecar/dns.js +24 -2
- package/dist/sidecar/fake-web.d.ts +1 -0
- package/dist/sidecar/fake-web.js +70 -17
- package/dist/sidecar/http.d.ts +29 -0
- package/dist/sidecar/http.js +105 -3
- package/dist/sidecar/relay.d.ts +1 -0
- package/dist/sidecar/relay.js +70 -22
- package/dist/sidecar/sidecar.d.ts +3 -0
- package/dist/sidecar/sidecar.js +6 -2
- package/dist/sidecar/state.d.ts +3 -0
- package/dist/sidecar/state.js +5 -0
- package/dist/sidecar/tool-hook.d.ts +1 -0
- package/dist/sidecar/tool-hook.js +78 -29
- package/dist/sidecar-bundle/sidecar.mjs +410 -78
- package/dist/types.d.ts +1 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type SidecarPorts } from "./config.js";
|
|
2
2
|
import { type Ips } from "./dns.js";
|
|
3
|
+
import { type RequestLimits } from "./http.js";
|
|
3
4
|
import { SidecarState } from "./state.js";
|
|
4
5
|
export type SidecarOptions = {
|
|
5
6
|
controlSecret: string;
|
|
@@ -11,6 +12,8 @@ export type SidecarOptions = {
|
|
|
11
12
|
fetch?: typeof fetch;
|
|
12
13
|
checkTimeoutMs?: number;
|
|
13
14
|
upstreamIdleTimeoutMs?: number;
|
|
15
|
+
requestLimits?: Partial<RequestLimits>;
|
|
16
|
+
requestTimeoutMs?: number;
|
|
14
17
|
lease?: {
|
|
15
18
|
ms: number;
|
|
16
19
|
onExpired: () => void;
|
package/dist/sidecar/sidecar.js
CHANGED
|
@@ -5,6 +5,7 @@ import {} from "./config.js";
|
|
|
5
5
|
import { createControl } from "./control.js";
|
|
6
6
|
import { createDns } from "./dns.js";
|
|
7
7
|
import { createFakeHttp, createFakeHttps, generateTls } from "./fake-web.js";
|
|
8
|
+
import { REQUEST_TIMEOUT_MS } from "./http.js";
|
|
8
9
|
import { createRelay, UPSTREAM_IDLE_TIMEOUT_MS } from "./relay.js";
|
|
9
10
|
import { SidecarState } from "./state.js";
|
|
10
11
|
import { CHECK_TIMEOUT_MS, createToolHook } from "./tool-hook.js";
|
|
@@ -34,7 +35,7 @@ const close = (server) => new Promise((resolve) => {
|
|
|
34
35
|
server.closeAllConnections();
|
|
35
36
|
});
|
|
36
37
|
export async function startSidecar(options) {
|
|
37
|
-
const state = new SidecarState();
|
|
38
|
+
const state = new SidecarState(options.requestLimits);
|
|
38
39
|
const host = options.host ?? "0.0.0.0";
|
|
39
40
|
const trustTestSourceHeader = options.trustTestSourceHeader ?? false;
|
|
40
41
|
const { domain } = options;
|
|
@@ -49,7 +50,8 @@ export async function startSidecar(options) {
|
|
|
49
50
|
}, Math.max(1, Math.floor(lease.ms / 4)))
|
|
50
51
|
: undefined;
|
|
51
52
|
leaseTimer?.unref();
|
|
52
|
-
const
|
|
53
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
54
|
+
const web = { state, domain, trustTestSourceHeader, requestTimeoutMs };
|
|
53
55
|
const servers = {
|
|
54
56
|
http: createFakeHttp(web),
|
|
55
57
|
https: createFakeHttps(web, tls),
|
|
@@ -57,6 +59,7 @@ export async function startSidecar(options) {
|
|
|
57
59
|
state,
|
|
58
60
|
trustTestSourceHeader,
|
|
59
61
|
idleTimeoutMs: options.upstreamIdleTimeoutMs ?? UPSTREAM_IDLE_TIMEOUT_MS,
|
|
62
|
+
requestTimeoutMs,
|
|
60
63
|
}),
|
|
61
64
|
hook: createToolHook({
|
|
62
65
|
state,
|
|
@@ -64,6 +67,7 @@ export async function startSidecar(options) {
|
|
|
64
67
|
signal: controller.signal,
|
|
65
68
|
trustTestSourceHeader,
|
|
66
69
|
timeoutMs: options.checkTimeoutMs ?? CHECK_TIMEOUT_MS,
|
|
70
|
+
requestTimeoutMs,
|
|
67
71
|
}),
|
|
68
72
|
control: createControl({
|
|
69
73
|
state,
|
package/dist/sidecar/state.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type IncomingMessage } from "node:http";
|
|
2
|
+
import { RequestBudget, type RequestLimits } from "./http.js";
|
|
2
3
|
import { type Hit, type ModelCall, type ModelProxy, type SecretMatch, type Secrets, type ToolCall, type ToolCallCheck } from "../types.js";
|
|
3
4
|
export declare const MAX_ENTRIES = 10000;
|
|
4
5
|
export declare const MAX_LOG_BYTES: number;
|
|
@@ -37,6 +38,8 @@ export declare function drain<T>(log: Log<T>): Log<T>;
|
|
|
37
38
|
export declare class SidecarState {
|
|
38
39
|
readonly runs: Map<string, RunState>;
|
|
39
40
|
readonly unattributed: Log<Hit>;
|
|
41
|
+
readonly requests: RequestBudget;
|
|
42
|
+
constructor(limits?: Partial<RequestLimits>);
|
|
40
43
|
register(id: string, run: Pick<RunState, "ip" | "pages" | "secrets" | "modelProxy" | "toolCallCheck">): "ok" | "run" | "ip";
|
|
41
44
|
runEntryForIp(ip: string): [string, RunState] | undefined;
|
|
42
45
|
runForIp(ip: string): RunState | undefined;
|
package/dist/sidecar/state.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {} from "node:http";
|
|
2
2
|
import { findSecrets } from "../secrets.js";
|
|
3
|
+
import { RequestBudget } from "./http.js";
|
|
3
4
|
import {} from "../types.js";
|
|
4
5
|
export const MAX_ENTRIES = 10_000;
|
|
5
6
|
export const MAX_LOG_BYTES = 4 * 1024 * 1024;
|
|
@@ -61,6 +62,10 @@ export function drain(log) {
|
|
|
61
62
|
export class SidecarState {
|
|
62
63
|
runs = new Map();
|
|
63
64
|
unattributed = emptyLog();
|
|
65
|
+
requests;
|
|
66
|
+
constructor(limits) {
|
|
67
|
+
this.requests = new RequestBudget(limits);
|
|
68
|
+
}
|
|
64
69
|
register(id, run) {
|
|
65
70
|
if (this.runs.has(id))
|
|
66
71
|
return "run";
|
|
@@ -2,7 +2,7 @@ import {} from "node:http";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { findSecrets } from "../secrets.js";
|
|
4
4
|
import { DETAIL_LIMIT, isRecord, messageOf, } from "../types.js";
|
|
5
|
-
import { BodyTooLargeError, jsonServer, readBody, readJson, sendJson, } from "./http.js";
|
|
5
|
+
import { BodyTooLargeError, BODY_LIMIT_BYTES, jsonServer, readBody, readJson, sendJson, } from "./http.js";
|
|
6
6
|
import { push, sourceIp } from "./state.js";
|
|
7
7
|
export const CHECK_TIMEOUT_MS = 25_000;
|
|
8
8
|
const hookSchema = z.object({
|
|
@@ -111,37 +111,86 @@ export function createToolHook(context) {
|
|
|
111
111
|
at: new Date(started).toISOString(),
|
|
112
112
|
error: { by: "sidecar", code, detail: detail.slice(0, DETAIL_LIMIT) },
|
|
113
113
|
});
|
|
114
|
-
|
|
114
|
+
if (!res.destroyed)
|
|
115
|
+
sendJson(res, status, {}, { Connection: "close" });
|
|
115
116
|
};
|
|
116
|
-
|
|
117
|
+
const lease = context.state.requests.take(source);
|
|
118
|
+
if (!lease)
|
|
119
|
+
return refuse(429, "request_limit", "too many in-flight requests");
|
|
120
|
+
const controller = new AbortController();
|
|
121
|
+
let handlerDone = false;
|
|
122
|
+
let responseDone = false;
|
|
123
|
+
let timedOut = false;
|
|
124
|
+
const release = () => {
|
|
125
|
+
if (handlerDone && responseDone)
|
|
126
|
+
lease.release();
|
|
127
|
+
};
|
|
128
|
+
res.once("finish", () => {
|
|
129
|
+
responseDone = true;
|
|
130
|
+
release();
|
|
131
|
+
});
|
|
132
|
+
res.once("close", () => {
|
|
133
|
+
responseDone = true;
|
|
134
|
+
controller.abort();
|
|
135
|
+
release();
|
|
136
|
+
});
|
|
137
|
+
req.once("aborted", () => {
|
|
138
|
+
controller.abort();
|
|
139
|
+
});
|
|
140
|
+
req.once("error", () => {
|
|
141
|
+
controller.abort();
|
|
142
|
+
});
|
|
143
|
+
const timer = setTimeout(() => {
|
|
144
|
+
timedOut = true;
|
|
145
|
+
req.destroy(new Error("request body timed out"));
|
|
146
|
+
}, context.requestTimeoutMs);
|
|
147
|
+
timer.unref();
|
|
148
|
+
req.once("end", () => clearTimeout(timer));
|
|
117
149
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
150
|
+
if (!lease.reserve(2 * BODY_LIMIT_BYTES)) {
|
|
151
|
+
return refuse(429, "buffer_limit", "too many buffered request bytes");
|
|
152
|
+
}
|
|
153
|
+
let payload;
|
|
154
|
+
try {
|
|
155
|
+
payload = await readJson(req);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (timedOut)
|
|
159
|
+
return refuse(408, "request_timeout", "request body timed out");
|
|
160
|
+
return error instanceof BodyTooLargeError
|
|
161
|
+
? refuse(413, "payload_too_large", messageOf(error))
|
|
162
|
+
: refuse(400, "invalid_payload", messageOf(error));
|
|
163
|
+
}
|
|
164
|
+
const hook = hookSchema.safeParse(payload);
|
|
165
|
+
if (!hook.success) {
|
|
166
|
+
return refuse(400, "invalid_payload", hook.error.message, payload);
|
|
167
|
+
}
|
|
168
|
+
const check = owned.toolCallCheck;
|
|
169
|
+
const checked = check
|
|
170
|
+
? await askCheck({
|
|
171
|
+
...context,
|
|
172
|
+
signal: AbortSignal.any([context.signal, controller.signal]),
|
|
173
|
+
}, check, runId, payload)
|
|
174
|
+
: { response: {} };
|
|
175
|
+
push(owned.toolCalls, {
|
|
176
|
+
toolUseId: hook.data.tool_use_id,
|
|
177
|
+
tool: hook.data.tool_name,
|
|
178
|
+
input: hook.data.tool_input,
|
|
179
|
+
hookPayload: payload,
|
|
180
|
+
response: checked.response,
|
|
181
|
+
action: actionOf(checked.response),
|
|
182
|
+
checked: check !== undefined,
|
|
183
|
+
latencyMs: Date.now() - started,
|
|
184
|
+
at: new Date(started).toISOString(),
|
|
185
|
+
...(checked.error ? { error: checked.error } : {}),
|
|
186
|
+
}, findSecrets(String(JSON.stringify(hook.data.tool_input)), owned.secrets));
|
|
187
|
+
if (!res.destroyed)
|
|
188
|
+
sendJson(res, 200, checked.response);
|
|
124
189
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
190
|
+
finally {
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
handlerDone = true;
|
|
193
|
+
release();
|
|
128
194
|
}
|
|
129
|
-
const check = owned.toolCallCheck;
|
|
130
|
-
const checked = check
|
|
131
|
-
? await askCheck(context, check, runId, payload)
|
|
132
|
-
: { response: {} };
|
|
133
|
-
push(owned.toolCalls, {
|
|
134
|
-
toolUseId: hook.data.tool_use_id,
|
|
135
|
-
tool: hook.data.tool_name,
|
|
136
|
-
input: hook.data.tool_input,
|
|
137
|
-
hookPayload: payload,
|
|
138
|
-
response: checked.response,
|
|
139
|
-
action: actionOf(checked.response),
|
|
140
|
-
checked: check !== undefined,
|
|
141
|
-
latencyMs: Date.now() - started,
|
|
142
|
-
at: new Date(started).toISOString(),
|
|
143
|
-
...(checked.error ? { error: checked.error } : {}),
|
|
144
|
-
}, findSecrets(String(JSON.stringify(hook.data.tool_input)), owned.secrets));
|
|
145
|
-
sendJson(res, 200, checked.response);
|
|
146
195
|
});
|
|
147
196
|
}
|