runtimeuse 0.2.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/.env.example +4 -0
- package/README.md +222 -0
- package/dist/agent-handler.d.ts +26 -0
- package/dist/agent-handler.d.ts.map +1 -0
- package/dist/agent-handler.js +2 -0
- package/dist/agent-handler.js.map +1 -0
- package/dist/artifact-manager.d.ts +27 -0
- package/dist/artifact-manager.d.ts.map +1 -0
- package/dist/artifact-manager.js +125 -0
- package/dist/artifact-manager.js.map +1 -0
- package/dist/artifact-manager.test.d.ts +2 -0
- package/dist/artifact-manager.test.d.ts.map +1 -0
- package/dist/artifact-manager.test.js +251 -0
- package/dist/artifact-manager.test.js.map +1 -0
- package/dist/claude-handler.d.ts +3 -0
- package/dist/claude-handler.d.ts.map +1 -0
- package/dist/claude-handler.js +76 -0
- package/dist/claude-handler.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +87 -0
- package/dist/cli.js.map +1 -0
- package/dist/command-handler.d.ts +22 -0
- package/dist/command-handler.d.ts.map +1 -0
- package/dist/command-handler.js +75 -0
- package/dist/command-handler.js.map +1 -0
- package/dist/command-handler.test.d.ts +2 -0
- package/dist/command-handler.test.d.ts.map +1 -0
- package/dist/command-handler.test.js +267 -0
- package/dist/command-handler.test.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +13 -0
- package/dist/constants.js.map +1 -0
- package/dist/default-handler.d.ts +3 -0
- package/dist/default-handler.d.ts.map +1 -0
- package/dist/default-handler.js +76 -0
- package/dist/default-handler.js.map +1 -0
- package/dist/download-handler.d.ts +8 -0
- package/dist/download-handler.d.ts.map +1 -0
- package/dist/download-handler.js +36 -0
- package/dist/download-handler.js.map +1 -0
- package/dist/download-handler.test.d.ts +2 -0
- package/dist/download-handler.test.d.ts.map +1 -0
- package/dist/download-handler.test.js +123 -0
- package/dist/download-handler.test.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +14 -0
- package/dist/logger.js.map +1 -0
- package/dist/openai-handler.d.ts +3 -0
- package/dist/openai-handler.d.ts.map +1 -0
- package/dist/openai-handler.js +86 -0
- package/dist/openai-handler.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +52 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +29 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +244 -0
- package/dist/session.js.map +1 -0
- package/dist/session.test.d.ts +2 -0
- package/dist/session.test.d.ts.map +1 -0
- package/dist/session.test.js +339 -0
- package/dist/session.test.js.map +1 -0
- package/dist/storage.d.ts +3 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +21 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/upload-tracker.d.ts +10 -0
- package/dist/upload-tracker.d.ts.map +1 -0
- package/dist/upload-tracker.js +27 -0
- package/dist/upload-tracker.js.map +1 -0
- package/dist/upload-tracker.test.d.ts +2 -0
- package/dist/upload-tracker.test.d.ts.map +1 -0
- package/dist/upload-tracker.test.js +89 -0
- package/dist/upload-tracker.test.js.map +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +32 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.test.d.ts +2 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/dist/utils.test.js +92 -0
- package/dist/utils.test.js.map +1 -0
- package/package.json +40 -0
- package/scripts/dev-publish.sh +45 -0
- package/src/agent-handler.ts +26 -0
- package/src/artifact-manager.test.ts +320 -0
- package/src/artifact-manager.ts +170 -0
- package/src/claude-handler.ts +95 -0
- package/src/cli.ts +107 -0
- package/src/command-handler.test.ts +507 -0
- package/src/command-handler.ts +102 -0
- package/src/constants.ts +12 -0
- package/src/download-handler.test.ts +183 -0
- package/src/download-handler.ts +45 -0
- package/src/index.ts +59 -0
- package/src/logger.ts +20 -0
- package/src/openai-handler.ts +120 -0
- package/src/server.ts +68 -0
- package/src/session.test.ts +448 -0
- package/src/session.ts +319 -0
- package/src/storage.ts +28 -0
- package/src/types.ts +101 -0
- package/src/upload-tracker.test.ts +112 -0
- package/src/upload-tracker.ts +30 -0
- package/src/utils.test.ts +120 -0
- package/src/utils.ts +35 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +7 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# runtimeuse (Runtime)
|
|
2
|
+
|
|
3
|
+
TypeScript runtime package for [runtimeuse](https://github.com/getlark/runtimeuse). Runs inside the sandbox and handles the agent lifecycle: receives invocations over WebSocket, executes your agent handler, manages artifact uploads, runs pre-commands, downloads runtime files, and sends structured results back to the client.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install runtimeuse
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
Run the runtime inside any sandbox:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx -y runtimeuse
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This starts a WebSocket server on port 8080 using the OpenAI agent handler (default). You can choose between built-in handlers:
|
|
20
|
+
|
|
21
|
+
- **`openai`** (default) -- uses `@openai/agents` SDK
|
|
22
|
+
- **`claude`** -- uses `@anthropic-ai/claude-agent-sdk` with Claude Code tools and `bypassPermissions` mode
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx -y runtimeuse # OpenAI (default)
|
|
26
|
+
npx -y runtimeuse --agent claude # Claude
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Use it programmatically:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { RuntimeUseServer, openaiHandler, claudeHandler } from "runtimeuse";
|
|
33
|
+
|
|
34
|
+
const server = new RuntimeUseServer({ handler: openaiHandler, port: 8080 });
|
|
35
|
+
await server.start();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Custom Handler
|
|
39
|
+
|
|
40
|
+
Implement `AgentHandler` to plug in your own agent:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { RuntimeUseServer } from "runtimeuse";
|
|
44
|
+
import type {
|
|
45
|
+
AgentHandler,
|
|
46
|
+
AgentInvocation,
|
|
47
|
+
AgentResult,
|
|
48
|
+
MessageSender,
|
|
49
|
+
} from "runtimeuse";
|
|
50
|
+
|
|
51
|
+
const handler: AgentHandler = {
|
|
52
|
+
async run(
|
|
53
|
+
invocation: AgentInvocation,
|
|
54
|
+
sender: MessageSender,
|
|
55
|
+
): Promise<AgentResult> {
|
|
56
|
+
sender.sendAssistantMessage(["Running agent..."]);
|
|
57
|
+
|
|
58
|
+
const output = await myAgent(
|
|
59
|
+
invocation.systemPrompt,
|
|
60
|
+
invocation.userPrompt,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
structuredOutput: output,
|
|
65
|
+
metadata: { duration_ms: 1500 },
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const server = new RuntimeUseServer({ handler, port: 8080 });
|
|
71
|
+
await server.start();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Core Concept: AgentHandler
|
|
75
|
+
|
|
76
|
+
The `AgentHandler` interface is the single integration point. Implement `run()` to plug in any agent.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface AgentHandler {
|
|
80
|
+
run(invocation: AgentInvocation, sender: MessageSender): Promise<AgentResult>;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**`AgentInvocation`** -- everything your agent needs:
|
|
85
|
+
|
|
86
|
+
| Field | Type | Description |
|
|
87
|
+
| -------------- | ---------------------------------------------------------- | ------------------------------------ |
|
|
88
|
+
| `systemPrompt` | `string` | System prompt for the agent |
|
|
89
|
+
| `userPrompt` | `string` | User prompt / task description |
|
|
90
|
+
| `outputFormat` | `{ type: "json_schema"; schema: Record<string, unknown> }` | Expected output schema |
|
|
91
|
+
| `model` | `string` | Model identifier |
|
|
92
|
+
| `secrets` | `string[]` | Values to redact from logs |
|
|
93
|
+
| `env` | `Record<string, string>` | Environment variables for the agent |
|
|
94
|
+
| `signal` | `AbortSignal` | Observe for cancellation (read-only) |
|
|
95
|
+
| `logger` | `Logger` | Prefixed logger for this invocation |
|
|
96
|
+
|
|
97
|
+
**`MessageSender`** -- send intermediate messages back to the client:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
sender.sendAssistantMessage(["Step 1: Navigating to login page..."]);
|
|
101
|
+
sender.sendErrorMessage("Something went wrong", { code: "TIMEOUT" });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**`AgentResult`** -- what your handler returns:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
interface AgentResult {
|
|
108
|
+
structuredOutput: Record<string, unknown>; // the main result payload
|
|
109
|
+
metadata?: Record<string, unknown>; // optional metadata (timing, cost, etc.)
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Server Options
|
|
114
|
+
|
|
115
|
+
### CLI
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx runtimeuse # OpenAI handler (default)
|
|
119
|
+
npx runtimeuse --agent claude # Claude handler
|
|
120
|
+
npx runtimeuse --handler ./my-handler.js # custom handler
|
|
121
|
+
npx runtimeuse --port 3000 # custom port
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Programmatic
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { RuntimeUseServer } from "runtimeuse";
|
|
128
|
+
|
|
129
|
+
const server = new RuntimeUseServer({
|
|
130
|
+
handler: myHandler,
|
|
131
|
+
port: 8080, // default: 8080
|
|
132
|
+
defaultModel: "gpt-4.1",
|
|
133
|
+
uploadTimeoutMs: 30_000,
|
|
134
|
+
artifactWaitMs: 60_000,
|
|
135
|
+
postInvocationDelayMs: 3_000,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await server.start();
|
|
139
|
+
// ... later
|
|
140
|
+
await server.stop();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Direct Session Usage
|
|
144
|
+
|
|
145
|
+
For custom WebSocket servers:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { WebSocketSession, UploadTracker } from "runtimeuse";
|
|
149
|
+
|
|
150
|
+
wss.on("connection", (ws) => {
|
|
151
|
+
const session = new WebSocketSession(ws, {
|
|
152
|
+
handler: myHandler,
|
|
153
|
+
uploadTracker: new UploadTracker(),
|
|
154
|
+
});
|
|
155
|
+
session.run();
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Invocation Lifecycle
|
|
160
|
+
|
|
161
|
+
When a client sends an `invocation_message`, the session:
|
|
162
|
+
|
|
163
|
+
1. **Downloads runtime files** -- if `runtime_environment_downloadables` is set, fetches and extracts them
|
|
164
|
+
2. **Runs pre-commands** -- if `pre_agent_invocation_commands` is set, executes them. If a command exits 0, its result becomes the final result (agent is skipped). If it exits non-zero, a failure result is sent
|
|
165
|
+
3. **Calls `handler.run()`** -- your agent logic runs with the invocation context and a `MessageSender`
|
|
166
|
+
4. **Sends `result_message`** -- the `AgentResult` from your handler is sent back to the client
|
|
167
|
+
5. **Finalizes** -- stops artifact watching, waits for pending uploads, closes the WebSocket
|
|
168
|
+
|
|
169
|
+
## Artifact Management
|
|
170
|
+
|
|
171
|
+
Files written to the artifacts directory are automatically detected via `chokidar` file watching and uploaded through a presigned URL handshake with the client. The artifacts directory is specified per-invocation via the `artifacts_dir` field in the `InvocationMessage` (defaults to `/tmp/artifacts`).
|
|
172
|
+
|
|
173
|
+
- File type is inferred from extension (`.png` -> `screenshot`, `.webm` -> `video`, etc.)
|
|
174
|
+
- `.artifactignore` files are respected (same syntax as `.gitignore`)
|
|
175
|
+
- Default ignore patterns exclude `node_modules/`, `dist/`, `__pycache__/`, virtual environments, etc.
|
|
176
|
+
|
|
177
|
+
## Secret Redaction
|
|
178
|
+
|
|
179
|
+
The `redactSecrets` utility recursively replaces secret values in strings, arrays, and objects:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { redactSecrets } from "runtimeuse";
|
|
183
|
+
|
|
184
|
+
const safe = redactSecrets("token=sk-abc123", ["sk-abc123"]);
|
|
185
|
+
// "token=[REDACTED]"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Command output (stdout/stderr) from pre-commands is automatically redacted using the command's environment variable values.
|
|
189
|
+
|
|
190
|
+
## API Reference
|
|
191
|
+
|
|
192
|
+
### Classes
|
|
193
|
+
|
|
194
|
+
| Class | Description |
|
|
195
|
+
| ------------------ | ---------------------------------------------------------------- |
|
|
196
|
+
| `RuntimeUseServer` | Standalone WebSocket server that creates sessions per connection |
|
|
197
|
+
| `WebSocketSession` | Manages a single WebSocket connection lifecycle |
|
|
198
|
+
| `ArtifactManager` | Watches a directory and handles the upload handshake |
|
|
199
|
+
| `UploadTracker` | Tracks in-flight uploads with timeout support |
|
|
200
|
+
| `CommandHandler` | Executes shell commands with secret redaction and abort support |
|
|
201
|
+
| `DownloadHandler` | Downloads files via `fetch()` with automatic zip extraction |
|
|
202
|
+
|
|
203
|
+
### Functions
|
|
204
|
+
|
|
205
|
+
| Function | Description |
|
|
206
|
+
| ------------------------------------ | -------------------------------------------------- |
|
|
207
|
+
| `uploadFile(path, url, contentType)` | Upload a file to a presigned URL |
|
|
208
|
+
| `redactSecrets(value, secrets)` | Recursively redact secrets from any data structure |
|
|
209
|
+
| `createLogger(sourceId)` | Create a prefixed logger |
|
|
210
|
+
| `sleep(ms)` | Promise-based sleep |
|
|
211
|
+
|
|
212
|
+
### Protocol Message Types
|
|
213
|
+
|
|
214
|
+
| Type | Direction | Description |
|
|
215
|
+
| ------------------------------- | ----------------- | --------------------------------------- |
|
|
216
|
+
| `InvocationMessage` | Client -> Runtime | Start an agent invocation |
|
|
217
|
+
| `CancelMessage` | Client -> Runtime | Cancel a running invocation |
|
|
218
|
+
| `ArtifactUploadResponseMessage` | Client -> Runtime | Presigned URL for artifact upload |
|
|
219
|
+
| `ResultMessage` | Runtime -> Client | Structured agent result |
|
|
220
|
+
| `AssistantMessage` | Runtime -> Client | Intermediate text from the agent |
|
|
221
|
+
| `ArtifactUploadRequestMessage` | Runtime -> Client | Request a presigned URL for an artifact |
|
|
222
|
+
| `ErrorMessage` | Runtime -> Client | Error during execution |
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Logger } from "./logger.js";
|
|
2
|
+
export interface AgentInvocation {
|
|
3
|
+
systemPrompt: string;
|
|
4
|
+
userPrompt: string;
|
|
5
|
+
outputFormat: {
|
|
6
|
+
type: "json_schema";
|
|
7
|
+
schema: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
model: string;
|
|
10
|
+
secrets: string[];
|
|
11
|
+
env: Record<string, string>;
|
|
12
|
+
signal: AbortSignal;
|
|
13
|
+
logger: Logger;
|
|
14
|
+
}
|
|
15
|
+
export interface AgentResult {
|
|
16
|
+
structuredOutput: Record<string, unknown>;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export interface MessageSender {
|
|
20
|
+
sendAssistantMessage(textBlocks: string[]): void;
|
|
21
|
+
sendErrorMessage(error: string, metadata?: Record<string, unknown>): void;
|
|
22
|
+
}
|
|
23
|
+
export interface AgentHandler {
|
|
24
|
+
run(invocation: AgentInvocation, sender: MessageSender): Promise<AgentResult>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=agent-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-handler.d.ts","sourceRoot":"","sources":["../src/agent-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACjD,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC/E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-handler.js","sourceRoot":"","sources":["../src/agent-handler.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { UploadTracker } from "./upload-tracker.js";
|
|
2
|
+
import type { ArtifactUploadRequestMessage, ArtifactUploadResponseMessage } from "./types.js";
|
|
3
|
+
import { type Logger } from "./logger.js";
|
|
4
|
+
export interface ArtifactManagerConfig {
|
|
5
|
+
artifactsDir: string;
|
|
6
|
+
uploadTracker: UploadTracker;
|
|
7
|
+
send: (message: ArtifactUploadRequestMessage) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare class ArtifactManager {
|
|
10
|
+
private readonly watcher;
|
|
11
|
+
private readonly pendingRequests;
|
|
12
|
+
private readonly artifactsDir;
|
|
13
|
+
private readonly uploadTracker;
|
|
14
|
+
private readonly send;
|
|
15
|
+
private ig;
|
|
16
|
+
private logger;
|
|
17
|
+
private loggingLevel;
|
|
18
|
+
constructor(config: ArtifactManagerConfig);
|
|
19
|
+
private reloadIgnorePatterns;
|
|
20
|
+
setLogger(logger: Logger): void;
|
|
21
|
+
handleUploadResponse(message: ArtifactUploadResponseMessage): Promise<void>;
|
|
22
|
+
waitForPendingRequests(timeoutMs: number): Promise<void>;
|
|
23
|
+
stopWatching(): Promise<void>;
|
|
24
|
+
private onFileEvent;
|
|
25
|
+
private requestUpload;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=artifact-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact-manager.d.ts","sourceRoot":"","sources":["../src/artifact-manager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC9B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAczD,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,IAAI,EAAE,CAAC,OAAO,EAAE,4BAA4B,KAAK,IAAI,CAAC;CACvD;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG5B;IACJ,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkD;IACvE,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,YAAY,CAA4B;gBAEpC,MAAM,EAAE,qBAAqB;IAgBzC,OAAO,CAAC,oBAAoB;IAW5B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIzB,oBAAoB,CACxB,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC;IA8BV,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAInC,OAAO,CAAC,WAAW;IA8BnB,OAAO,CAAC,aAAa;CAkBtB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chokidar from "chokidar";
|
|
4
|
+
import ignore from "ignore";
|
|
5
|
+
import { uploadFile } from "./storage.js";
|
|
6
|
+
import { DEFAULT_ARTIFACT_IGNORE } from "./constants.js";
|
|
7
|
+
import { defaultLogger } from "./logger.js";
|
|
8
|
+
const EXTENSION_TO_TYPE = {
|
|
9
|
+
".webm": "video",
|
|
10
|
+
".png": "screenshot",
|
|
11
|
+
".ndjson": "tool_calls",
|
|
12
|
+
".js": "javascript",
|
|
13
|
+
".ts": "javascript",
|
|
14
|
+
".py": "python",
|
|
15
|
+
".sh": "shellscript",
|
|
16
|
+
};
|
|
17
|
+
export class ArtifactManager {
|
|
18
|
+
watcher;
|
|
19
|
+
pendingRequests = new Map();
|
|
20
|
+
artifactsDir;
|
|
21
|
+
uploadTracker;
|
|
22
|
+
send;
|
|
23
|
+
ig = ignore();
|
|
24
|
+
logger = defaultLogger;
|
|
25
|
+
loggingLevel = "info";
|
|
26
|
+
constructor(config) {
|
|
27
|
+
this.artifactsDir = config.artifactsDir;
|
|
28
|
+
this.uploadTracker = config.uploadTracker;
|
|
29
|
+
this.send = config.send;
|
|
30
|
+
this.reloadIgnorePatterns();
|
|
31
|
+
this.watcher = chokidar.watch(config.artifactsDir, {
|
|
32
|
+
awaitWriteFinish: true,
|
|
33
|
+
alwaysStat: true,
|
|
34
|
+
});
|
|
35
|
+
this.watcher.on("add", (p, s) => this.onFileEvent(p, s));
|
|
36
|
+
this.watcher.on("change", (p, s) => this.onFileEvent(p, s));
|
|
37
|
+
}
|
|
38
|
+
reloadIgnorePatterns() {
|
|
39
|
+
this.ig = ignore();
|
|
40
|
+
const ignorePath = path.join(this.artifactsDir, ".artifactignore");
|
|
41
|
+
if (fs.existsSync(ignorePath)) {
|
|
42
|
+
this.ig.add(fs.readFileSync(ignorePath, "utf-8"));
|
|
43
|
+
this.logger.log(`Loaded .artifactignore from ${ignorePath}`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this.ig.add(DEFAULT_ARTIFACT_IGNORE);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
setLogger(logger) {
|
|
50
|
+
this.logger = logger;
|
|
51
|
+
}
|
|
52
|
+
async handleUploadResponse(message) {
|
|
53
|
+
this.logger.log(`Uploading artifact: ${message.filename} ${message.filepath}`);
|
|
54
|
+
const promise = uploadFile(message.filepath, message.presigned_url, message.content_type, this.logger);
|
|
55
|
+
this.uploadTracker.track(promise);
|
|
56
|
+
try {
|
|
57
|
+
await promise;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error instanceof Error && error.name === "ENOENT") {
|
|
61
|
+
this.logger.log(`Artifact file not found: ${message.filepath}`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
const pending = this.pendingRequests.get(message.filename);
|
|
67
|
+
if (pending) {
|
|
68
|
+
pending.resolve();
|
|
69
|
+
this.pendingRequests.delete(message.filename);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async waitForPendingRequests(timeoutMs) {
|
|
73
|
+
const promises = [...this.pendingRequests.values()].map((r) => r.promise);
|
|
74
|
+
if (promises.length === 0)
|
|
75
|
+
return;
|
|
76
|
+
this.logger.log(`Waiting for ${promises.length} artifact round-trips...`);
|
|
77
|
+
await Promise.race([
|
|
78
|
+
Promise.allSettled(promises),
|
|
79
|
+
new Promise((r) => setTimeout(r, timeoutMs)),
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
async stopWatching() {
|
|
83
|
+
await this.watcher.close();
|
|
84
|
+
}
|
|
85
|
+
onFileEvent(filePath, stats) {
|
|
86
|
+
if (this.loggingLevel === "debug") {
|
|
87
|
+
this.logger.log(`Artifact event: ${filePath}. Size: ${stats?.size ?? 0} bytes`);
|
|
88
|
+
}
|
|
89
|
+
if (path.basename(filePath) === ".artifactignore") {
|
|
90
|
+
this.reloadIgnorePatterns();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!stats?.isFile() || !stats.size) {
|
|
94
|
+
if (this.loggingLevel === "debug") {
|
|
95
|
+
this.logger.debug(`Skipping: ${filePath}`);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const relativePath = path.relative(this.artifactsDir, filePath);
|
|
100
|
+
if (!relativePath.startsWith("..") && this.ig.ignores(relativePath)) {
|
|
101
|
+
if (this.loggingLevel === "debug") {
|
|
102
|
+
this.logger.debug(`Skipping ignored artifact: ${relativePath}`);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.requestUpload(filePath);
|
|
107
|
+
}
|
|
108
|
+
requestUpload(filePath) {
|
|
109
|
+
const filename = path.basename(filePath);
|
|
110
|
+
const ext = path.extname(filePath);
|
|
111
|
+
let resolve;
|
|
112
|
+
const promise = new Promise((r) => {
|
|
113
|
+
resolve = r;
|
|
114
|
+
});
|
|
115
|
+
this.pendingRequests.set(filename, { promise, resolve });
|
|
116
|
+
this.logger.log(`Requesting upload for artifact: ${filename}`);
|
|
117
|
+
this.send({
|
|
118
|
+
message_type: "artifact_upload_request_message",
|
|
119
|
+
artifact_type: EXTENSION_TO_TYPE[ext] ?? "other",
|
|
120
|
+
filename,
|
|
121
|
+
filepath: filePath,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=artifact-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact-manager.js","sourceRoot":"","sources":["../src/artifact-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact-manager.test.d.ts","sourceRoot":"","sources":["../src/artifact-manager.test.ts"],"names":[],"mappings":""}
|