threadwire 0.1.11 → 0.1.12
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/CHANGELOG.md +10 -0
- package/README.md +10 -1
- package/TELEGRAM-INGRESS.md +2 -0
- package/docs/container-runtime.md +39 -3
- package/docs/isolated-provider-runtime.md +90 -12
- package/package.json +1 -1
- package/scripts/verify-package.js +1 -0
- package/src/absolute-deadline.js +8 -5
- package/src/cli.js +14 -3
- package/src/docker-api.js +100 -14
- package/src/isolated-runtime-client.js +116 -44
- package/src/isolated-runtime.js +673 -64
- package/src/isolated-state.js +62 -11
- package/src/isolated-worker.js +231 -23
- package/src/kimi-model-broker-policy.js +16 -5
- package/src/kimi-model-broker.js +134 -92
- package/src/model-broker-policy.js +11 -8
- package/src/model-broker.js +14 -0
- package/src/providers/kimi.js +15 -3
- package/src/telegram-ingress/core.js +7 -5
- package/src/telegram-webhook.js +10 -0
- package/src/threadwire-binding.js +192 -0
- package/src/workspace-profile.js +31 -8
- package/threadwire.workspace-profiles.json +3 -2
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/* eslint-disable jsdoc/require-jsdoc */
|
|
3
3
|
|
|
4
|
-
import {deadlineAfter, abortable, readResponseCapped} from "./absolute-deadline.js"
|
|
4
|
+
import {AbsoluteDeadline, deadlineAfter, abortable, readResponseCapped} from "./absolute-deadline.js"
|
|
5
5
|
|
|
6
6
|
const MAX_RESPONSE_BYTES = 2_097_152
|
|
7
|
-
const DEFAULT_TIMEOUT_MS = 3_660_000
|
|
8
7
|
const MAX_RAW_OUTPUT_BYTES = 1_048_576
|
|
9
8
|
|
|
10
9
|
export class IsolatedRuntimeClient {
|
|
@@ -13,18 +12,27 @@ export class IsolatedRuntimeClient {
|
|
|
13
12
|
this.url = normalizedUrl(options.url)
|
|
14
13
|
this.controlToken = nonempty(options.controlToken, "isolated runtime control token")
|
|
15
14
|
this.fetch = options.fetchImplementation ?? fetch
|
|
16
|
-
this.timeoutMs =
|
|
15
|
+
this.timeoutMs = optionalDuration(options.timeoutMs)
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
|
-
* @param {{provider: string, profile: string, repositoryRoot
|
|
19
|
+
* @param {{provider: string, profile: string, repositoryRoot?: string, cwd?: string, binding?: unknown, providerArguments: string[], resumeSession?: string, signal?: AbortSignal, deadline?: import("./absolute-deadline.js").AbsoluteDeadline}} request
|
|
21
20
|
* @returns {Promise<{preflightId: string, deadline?: import("./absolute-deadline.js").AbsoluteDeadline}>}
|
|
22
21
|
*/
|
|
23
22
|
async preflight(request) {
|
|
24
|
-
const deadline = request.deadline ?? deadlineAfter(this.timeoutMs, {signal: request.signal, timeoutMessage: "Isolated runtime launch timeout"})
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const deadline = request.deadline ?? (this.timeoutMs === undefined ? undefined : deadlineAfter(this.timeoutMs, {signal: request.signal, timeoutMessage: "Isolated runtime launch timeout"}))
|
|
24
|
+
const operation = deadline ?? new AbsoluteDeadline(undefined, {signal: request.signal, timeoutMessage: "Isolated runtime launch timeout"})
|
|
25
|
+
const body = {...request}
|
|
26
|
+
delete body.signal
|
|
27
|
+
delete body.deadline
|
|
28
|
+
if (deadline !== undefined) body.launchDeadlineAt = deadline.expiresAt
|
|
29
|
+
try {
|
|
30
|
+
const response = await this.request("/preflight", body, operation)
|
|
31
|
+
if (response.ok !== true || typeof response.preflightId !== "string") throw new Error("Isolated runtime preflight failed")
|
|
32
|
+
return deadline === undefined ? {preflightId: response.preflightId} : {preflightId: response.preflightId, deadline}
|
|
33
|
+
} finally {
|
|
34
|
+
if (deadline === undefined) operation.close()
|
|
35
|
+
}
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
/**
|
|
@@ -33,44 +41,42 @@ export class IsolatedRuntimeClient {
|
|
|
33
41
|
* resumeSession?: string,
|
|
34
42
|
* signal?: AbortSignal,
|
|
35
43
|
* deadline?: import("./absolute-deadline.js").AbsoluteDeadline,
|
|
36
|
-
* onEvent: (event: import("./types.js").WorkerEvent) =>
|
|
37
|
-
* onRecord?: (record: unknown) =>
|
|
38
|
-
* onStdoutChunk?: (chunk: Buffer) =>
|
|
39
|
-
* onStderrChunk?: (chunk: Buffer) =>
|
|
44
|
+
* onEvent: (event: import("./types.js").WorkerEvent) => unknown | Promise<unknown>,
|
|
45
|
+
* onRecord?: (record: unknown) => unknown | Promise<unknown>,
|
|
46
|
+
* onStdoutChunk?: (chunk: Buffer) => unknown | Promise<unknown>,
|
|
47
|
+
* onStderrChunk?: (chunk: Buffer) => unknown | Promise<unknown>
|
|
40
48
|
* }} options
|
|
49
|
+
* @returns {Promise<number>}
|
|
41
50
|
*/
|
|
42
51
|
async run(options) {
|
|
43
|
-
const deadline = options.deadline ?? deadlineAfter(this.timeoutMs, {signal: options.signal, timeoutMessage: "Isolated runtime launch timeout"})
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
const deadline = options.deadline ?? (this.timeoutMs === undefined ? undefined : deadlineAfter(this.timeoutMs, {signal: options.signal, timeoutMessage: "Isolated runtime launch timeout"}))
|
|
53
|
+
const operation = deadline ?? new AbsoluteDeadline(undefined, {signal: options.signal, timeoutMessage: "Isolated runtime launch timeout"})
|
|
54
|
+
try {
|
|
55
|
+
const response = await abortable(this.fetch(`${this.url}/run`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
redirect: "error",
|
|
58
|
+
headers: {
|
|
59
|
+
authorization: `Bearer ${this.controlToken}`,
|
|
60
|
+
"content-type": "application/json",
|
|
61
|
+
connection: "close",
|
|
62
|
+
...(operation.expiresAt === undefined ? {} : {"x-threadwire-launch-deadline": String(operation.expiresAt)})
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
preflightId: options.preflightId,
|
|
66
|
+
prompt: options.prompt,
|
|
67
|
+
providerArguments: options.providerArguments,
|
|
68
|
+
...(options.resumeSession === undefined ? {} : {resumeSession: options.resumeSession})
|
|
69
|
+
}),
|
|
70
|
+
signal: operation.signal
|
|
71
|
+
}), operation.signal)
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const bytes = await readResponseCapped(response, MAX_RESPONSE_BYTES, operation.signal)
|
|
74
|
+
throw new Error(safeRemoteError(bytes))
|
|
57
75
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
return {channel: item.channel, data}
|
|
62
|
-
})
|
|
63
|
-
for (const chunk of decoded) {
|
|
64
|
-
deadline.throwIfAborted()
|
|
65
|
-
const callback = chunk.channel === "stdout" ? options.onStdoutChunk : options.onStderrChunk
|
|
66
|
-
await abortable(callback?.(chunk.data), deadline.signal)
|
|
76
|
+
return await consumeRunStream(response, options, operation)
|
|
77
|
+
} finally {
|
|
78
|
+
if (options.deadline === undefined) operation.close()
|
|
67
79
|
}
|
|
68
|
-
for (const record of response.records) {
|
|
69
|
-
deadline.throwIfAborted()
|
|
70
|
-
await abortable(options.onRecord?.(record), deadline.signal)
|
|
71
|
-
if (isWorkerEventEnvelope(record)) await abortable(options.onEvent(record.event), deadline.signal)
|
|
72
|
-
}
|
|
73
|
-
return /** @type {number} */ (response.exitCode)
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
/** @param {string} path @param {unknown} body */
|
|
@@ -83,7 +89,7 @@ export class IsolatedRuntimeClient {
|
|
|
83
89
|
authorization: `Bearer ${this.controlToken}`,
|
|
84
90
|
"content-type": "application/json",
|
|
85
91
|
connection: "close",
|
|
86
|
-
"x-threadwire-launch-deadline": String(deadline.expiresAt)
|
|
92
|
+
...(deadline.expiresAt === undefined ? {} : {"x-threadwire-launch-deadline": String(deadline.expiresAt)})
|
|
87
93
|
},
|
|
88
94
|
body: JSON.stringify(body),
|
|
89
95
|
signal
|
|
@@ -106,8 +112,74 @@ export class IsolatedRuntimeClient {
|
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
/** @returns {Promise<number>} */
|
|
116
|
+
async function consumeRunStream(response, options, operation) {
|
|
117
|
+
const reader = response.body?.getReader()
|
|
118
|
+
if (reader === undefined) throw new Error("Isolated runtime emitted an invalid response")
|
|
119
|
+
let pending = Buffer.alloc(0)
|
|
120
|
+
let totalBytes = 0
|
|
121
|
+
let rawBytes = 0
|
|
122
|
+
/** @type {number | undefined} */
|
|
123
|
+
let terminal
|
|
124
|
+
const consume = async (line) => {
|
|
125
|
+
if (line.length === 0 || terminal !== undefined) throw new Error("Isolated runtime emitted an invalid response")
|
|
126
|
+
let frame
|
|
127
|
+
try { frame = JSON.parse(line.toString("utf8")) } catch { throw new Error("Isolated runtime emitted an invalid response") }
|
|
128
|
+
if (!isRecord(frame) || typeof frame.type !== "string") throw new Error("Isolated runtime emitted an invalid response")
|
|
129
|
+
if (frame.type === "chunk") {
|
|
130
|
+
if (Object.keys(frame).sort().join(",") !== "channel,data,type"
|
|
131
|
+
|| (frame.channel !== "stdout" && frame.channel !== "stderr") || typeof frame.data !== "string"
|
|
132
|
+
|| !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(frame.data)) {
|
|
133
|
+
throw new Error("Isolated runtime emitted an invalid response")
|
|
134
|
+
}
|
|
135
|
+
const data = Buffer.from(frame.data, "base64")
|
|
136
|
+
rawBytes += data.length
|
|
137
|
+
if (rawBytes > MAX_RAW_OUTPUT_BYTES) throw new Error("Isolated runtime emitted an invalid response")
|
|
138
|
+
const callback = frame.channel === "stdout" ? options.onStdoutChunk : options.onStderrChunk
|
|
139
|
+
await abortable(callback?.(data), operation.signal)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
if (frame.type === "record") {
|
|
143
|
+
if (Object.keys(frame).sort().join(",") !== "record,type") throw new Error("Isolated runtime emitted an invalid response")
|
|
144
|
+
await abortable(options.onRecord?.(frame.record), operation.signal)
|
|
145
|
+
if (isWorkerEventEnvelope(frame.record)) await abortable(options.onEvent(frame.record.event), operation.signal)
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
if (frame.type === "terminal" && Object.keys(frame).sort().join(",") === "exitCode,type" && Number.isSafeInteger(frame.exitCode)) {
|
|
149
|
+
terminal = frame.exitCode
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
throw new Error("Isolated runtime emitted an invalid response")
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
while (true) {
|
|
156
|
+
const {done, value} = await abortable(reader.read(), operation.signal)
|
|
157
|
+
if (done) break
|
|
158
|
+
const chunk = Buffer.from(value)
|
|
159
|
+
totalBytes += chunk.length
|
|
160
|
+
if (totalBytes > MAX_RESPONSE_BYTES) {
|
|
161
|
+
await reader.cancel(new Error("Response exceeded capacity")).catch(() => {})
|
|
162
|
+
throw new Error("Isolated runtime response exceeded the configured capacity")
|
|
163
|
+
}
|
|
164
|
+
pending = Buffer.concat([pending, chunk])
|
|
165
|
+
while (true) {
|
|
166
|
+
const newline = pending.indexOf(0x0a)
|
|
167
|
+
if (newline < 0) break
|
|
168
|
+
const line = pending.subarray(0, newline)
|
|
169
|
+
pending = pending.subarray(newline + 1)
|
|
170
|
+
await consume(line)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (pending.length !== 0 || terminal === undefined) throw new Error("Isolated runtime emitted an invalid response")
|
|
174
|
+
return terminal
|
|
175
|
+
} finally {
|
|
176
|
+
if (operation.signal.aborted) await reader.cancel(operation.signal.reason).catch(() => {})
|
|
177
|
+
reader.releaseLock()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function optionalDuration(value) {
|
|
182
|
+
if (value === undefined || (typeof value === "string" && value.trim().length === 0)) return undefined
|
|
111
183
|
const number = Number(value)
|
|
112
184
|
if (!Number.isSafeInteger(number) || number < 1 || number > 604_800_000) throw new Error("Invalid isolated runtime client timeout")
|
|
113
185
|
return number
|