@addai/node 0.30.13 → 0.30.15
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/dist/auto-update.d.ts +3 -0
- package/dist/auto-update.js +15 -0
- package/dist/capabilities.d.ts +17 -3
- package/dist/capabilities.js +18 -8
- package/dist/claude-models.d.ts +4 -12
- package/dist/claude-models.js +12 -88
- package/dist/claude-print.d.ts +15 -0
- package/dist/claude-print.js +16 -0
- package/dist/codex-models.d.ts +81 -0
- package/dist/codex-models.js +385 -0
- package/dist/codex-spawn.d.ts +19 -8
- package/dist/codex-spawn.js +68 -16
- package/dist/command-runner.js +31 -9
- package/dist/desktop/docker.js +2 -0
- package/dist/desktop/manager.d.ts +2 -1
- package/dist/desktop/manager.js +51 -1
- package/dist/desktop/provider.d.ts +1 -1
- package/dist/desktop/provider.js +2 -0
- package/dist/desktop/relay-client.js +7 -0
- package/dist/desktop/spec.d.ts +2 -0
- package/dist/direct/aios-bridge.d.ts +13 -0
- package/dist/direct/aios-bridge.js +177 -0
- package/dist/direct/aios-mcp.d.ts +2 -0
- package/dist/direct/aios-mcp.js +132 -0
- package/dist/direct/aios-tools.d.ts +351 -0
- package/dist/direct/aios-tools.js +120 -0
- package/dist/direct/board-chat-provider.d.ts +64 -0
- package/dist/direct/board-chat-provider.js +154 -0
- package/dist/direct/board-chat.d.ts +40 -0
- package/dist/direct/board-chat.js +414 -0
- package/dist/direct/config.d.ts +2 -0
- package/dist/direct/config.js +60 -0
- package/dist/direct/launch.d.ts +14 -0
- package/dist/direct/launch.js +121 -0
- package/dist/direct/protocol.d.ts +46 -0
- package/dist/direct/protocol.js +84 -0
- package/dist/direct/relay-main.d.ts +1 -0
- package/dist/direct/relay-main.js +21 -0
- package/dist/direct/relay.d.ts +26 -0
- package/dist/direct/relay.js +334 -0
- package/dist/direct/sessions.d.ts +61 -0
- package/dist/direct/sessions.js +368 -0
- package/dist/direct/supervisor.d.ts +9 -0
- package/dist/direct/supervisor.js +137 -0
- package/dist/direct/terminal.d.ts +1 -0
- package/dist/direct/terminal.js +106 -0
- package/dist/direct/worker.d.ts +1 -0
- package/dist/direct/worker.js +278 -0
- package/dist/entitybox/container-action.cjs +85 -0
- package/dist/entitybox/container-watchdog.cjs +70 -0
- package/dist/entitybox/model-entry.d.ts +3 -0
- package/dist/entitybox/model-entry.js +233 -0
- package/dist/entitybox/model-mcp-proxy.d.ts +3 -0
- package/dist/entitybox/model-mcp-proxy.js +85 -0
- package/dist/entitybox-application-boundary.d.ts +59 -0
- package/dist/entitybox-application-boundary.js +161 -0
- package/dist/entitybox-application-guard-core.d.ts +44 -0
- package/dist/entitybox-application-guard-core.js +164 -0
- package/dist/entitybox-application-guard-process.d.ts +1 -0
- package/dist/entitybox-application-guard-process.js +83 -0
- package/dist/entitybox-application-guard.d.ts +46 -0
- package/dist/entitybox-application-guard.js +239 -0
- package/dist/entitybox-capacity-health-host.d.ts +12 -0
- package/dist/entitybox-capacity-health-host.js +80 -0
- package/dist/entitybox-capacity-health-worker.d.ts +22 -0
- package/dist/entitybox-capacity-health-worker.js +168 -0
- package/dist/entitybox-capacity-host.d.ts +17 -0
- package/dist/entitybox-capacity-host.js +155 -0
- package/dist/entitybox-capacity-owner-process.d.ts +1 -0
- package/dist/entitybox-capacity-owner-process.js +32 -0
- package/dist/entitybox-capacity-owner.d.ts +16 -0
- package/dist/entitybox-capacity-owner.js +90 -0
- package/dist/entitybox-capacity-programs.d.ts +7 -0
- package/dist/entitybox-capacity-programs.js +49 -0
- package/dist/entitybox-capacity-provisioner.d.ts +82 -0
- package/dist/entitybox-capacity-provisioner.js +404 -0
- package/dist/entitybox-capacity-publication.d.ts +29 -0
- package/dist/entitybox-capacity-publication.js +199 -0
- package/dist/entitybox-capacity-registration.d.ts +36 -0
- package/dist/entitybox-capacity-registration.js +75 -0
- package/dist/entitybox-capacity-worker.d.ts +29 -0
- package/dist/entitybox-capacity-worker.js +113 -0
- package/dist/entitybox-computer-task.d.ts +69 -0
- package/dist/entitybox-computer-task.js +176 -0
- package/dist/entitybox-controller-recovery.d.ts +40 -0
- package/dist/entitybox-controller-recovery.js +156 -0
- package/dist/entitybox-desktop-action-server.d.ts +34 -0
- package/dist/entitybox-desktop-action-server.js +176 -0
- package/dist/entitybox-desktop-admission.d.ts +32 -0
- package/dist/entitybox-desktop-admission.js +79 -0
- package/dist/entitybox-display-binding.d.ts +13 -0
- package/dist/entitybox-display-binding.js +31 -0
- package/dist/entitybox-execution-container.d.ts +82 -0
- package/dist/entitybox-execution-container.js +450 -0
- package/dist/entitybox-execution-host.d.ts +40 -0
- package/dist/entitybox-execution-host.js +168 -0
- package/dist/entitybox-execution-lease.d.ts +81 -0
- package/dist/entitybox-execution-lease.js +332 -0
- package/dist/entitybox-file-inputs.d.ts +51 -0
- package/dist/entitybox-file-inputs.js +182 -0
- package/dist/entitybox-file-session.d.ts +38 -0
- package/dist/entitybox-file-session.js +82 -0
- package/dist/entitybox-host-process.d.ts +12 -0
- package/dist/entitybox-host-process.js +120 -0
- package/dist/entitybox-install.d.ts +10 -0
- package/dist/entitybox-install.js +40 -0
- package/dist/entitybox-mcp-broker.d.ts +77 -0
- package/dist/entitybox-mcp-broker.js +420 -0
- package/dist/entitybox-mcp-contract.d.ts +10 -0
- package/dist/entitybox-mcp-contract.js +24 -0
- package/dist/entitybox-mcp-guard.d.ts +3 -0
- package/dist/entitybox-mcp-guard.js +167 -0
- package/dist/entitybox-model-artifact.d.ts +9 -0
- package/dist/entitybox-model-artifact.js +83 -0
- package/dist/entitybox-native-services.d.ts +33 -0
- package/dist/entitybox-native-services.js +172 -0
- package/dist/entitybox-operation.d.ts +4 -0
- package/dist/entitybox-operation.js +34 -0
- package/dist/entitybox-provider-broker.d.ts +41 -0
- package/dist/entitybox-provider-broker.js +149 -0
- package/dist/entitybox-queue-request.d.ts +32 -0
- package/dist/entitybox-queue-request.js +141 -0
- package/dist/entitybox-recovery-host.d.ts +12 -0
- package/dist/entitybox-recovery-host.js +80 -0
- package/dist/entitybox-recovery-store.d.ts +33 -0
- package/dist/entitybox-recovery-store.js +150 -0
- package/dist/entitybox-recovery-worker.d.ts +30 -0
- package/dist/entitybox-recovery-worker.js +134 -0
- package/dist/entitybox-vault-provider.d.ts +27 -0
- package/dist/entitybox-vault-provider.js +127 -0
- package/dist/harness-registry.js +3 -1
- package/dist/index.js +72 -38
- package/dist/install.d.ts +14 -2
- package/dist/install.js +115 -10
- package/dist/model-cache.d.ts +38 -0
- package/dist/model-cache.js +137 -0
- package/dist/request-pump.d.ts +3 -0
- package/dist/request-pump.js +21 -7
- package/dist/session-runner.d.ts +8 -1
- package/dist/session-runner.js +56 -3
- package/dist/supabase-client.js +4 -1
- package/package.json +5 -1
- package/scripts/copy-assets.js +4 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { EntityboxFileInputs } from './entitybox-file-inputs';
|
|
2
|
+
import type { EntityboxRecoveryHost } from './entitybox-recovery-store';
|
|
3
|
+
import type { RuntimeEvent } from './types';
|
|
4
|
+
import { type EntityboxMcpScope } from './entitybox-mcp-contract';
|
|
5
|
+
type Rpc = <T>(name: string, args: Record<string, unknown>) => Promise<T>;
|
|
6
|
+
type Reason = 'completed' | 'canceled' | 'failed' | 'lease_lost';
|
|
7
|
+
export interface EntityboxExecutionIdentity {
|
|
8
|
+
requestId: string;
|
|
9
|
+
attemptId: string;
|
|
10
|
+
scope: EntityboxMcpScope;
|
|
11
|
+
}
|
|
12
|
+
export interface EntityboxProviderSelection {
|
|
13
|
+
kind: 'anthropic_api_key';
|
|
14
|
+
vaultItemId: string;
|
|
15
|
+
model: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class EntityboxExecutionError extends Error {
|
|
18
|
+
}
|
|
19
|
+
/** Runtime-only authority. Neither pairing nor the lease nonce is exported to
|
|
20
|
+
* the model/container. The SQL contract grants 15 seconds from execution of a
|
|
21
|
+
* fresh RPC. Our monotonic deadline starts BEFORE that RPC and reserves 2s.
|
|
22
|
+
* Claim is never automatically retried: its idempotent response cannot be used
|
|
23
|
+
* as a fresh 15-second grant. Container challenges always require a renewal. */
|
|
24
|
+
export declare class EntityboxExecutionLease {
|
|
25
|
+
private readonly token;
|
|
26
|
+
private readonly rpc;
|
|
27
|
+
readonly identity: Readonly<EntityboxExecutionIdentity>;
|
|
28
|
+
readonly signal: AbortSignal;
|
|
29
|
+
private readonly abort;
|
|
30
|
+
private readonly nonce;
|
|
31
|
+
private deadline;
|
|
32
|
+
private timer?;
|
|
33
|
+
private epoch?;
|
|
34
|
+
private renewing;
|
|
35
|
+
private challengeQueue;
|
|
36
|
+
private queuedChallenges;
|
|
37
|
+
private acknowledged;
|
|
38
|
+
private claimed;
|
|
39
|
+
private provider?;
|
|
40
|
+
private inventorySealed;
|
|
41
|
+
private constructor();
|
|
42
|
+
static claim(requestId: string, scope: EntityboxMcpScope, token: () => string, rpc: Rpc): Promise<EntityboxExecutionLease>;
|
|
43
|
+
private call;
|
|
44
|
+
private receipt;
|
|
45
|
+
private arm;
|
|
46
|
+
assertActive(): void;
|
|
47
|
+
/** A watchdog issues its own challenge BEFORE this method calls the server.
|
|
48
|
+
* It accepts the response only within its original challenge deadline, so a
|
|
49
|
+
* buffered/delayed grant can never restart or prolong an expired container. */
|
|
50
|
+
/** Independent model/application guards each require a fresh renewal. Their
|
|
51
|
+
* original challenge clocks still bound queued replies; no grant is shared. */
|
|
52
|
+
authorizeQueuedChallenge(): Promise<void>;
|
|
53
|
+
authorizeChallenge(): Promise<void>;
|
|
54
|
+
/** Events remain host-authenticated and server-checked. The model never
|
|
55
|
+
* receives the lease nonce. Queued output may drain after local process
|
|
56
|
+
* stop, but not after server expiry, cancellation or stop acknowledgement. */
|
|
57
|
+
appendEvent(event: RuntimeEvent): Promise<boolean>;
|
|
58
|
+
/** Metadata only: the native server rechecks the immutable request, current
|
|
59
|
+
* provider binding and active lease. This never renews the execution lease
|
|
60
|
+
* or returns a secret. A changed selection fences this attempt. */
|
|
61
|
+
providerSelection(): Promise<Readonly<EntityboxProviderSelection>>;
|
|
62
|
+
/** The nonce/pairing remain private to this host closure. Downloads happen
|
|
63
|
+
* only while the independent execution watchdog is renewing the lease. */
|
|
64
|
+
fileInputs(gatewayUrl: string, transport?: typeof fetch): EntityboxFileInputs;
|
|
65
|
+
/** Public immutable resource identity only. Registration happens before a
|
|
66
|
+
* guarded application resumes or a newly created executor starts. These RPCs
|
|
67
|
+
* never renew authority, and their receipts are checked against this lease. */
|
|
68
|
+
registerResource(kind: 'application' | 'display' | 'execution', binding: Record<string, unknown>): Promise<void>;
|
|
69
|
+
registerRecoveryHost(host: EntityboxRecoveryHost): Promise<void>;
|
|
70
|
+
sealInventory(hasExecution: boolean): Promise<void>;
|
|
71
|
+
assertInventorySealed(): void;
|
|
72
|
+
/** Atomic native event/status commit preserves cancellation and prevents
|
|
73
|
+
* duplicate terminal events after a lost response. Requires physical proof. */
|
|
74
|
+
finishRequest(outcome: Reason, completion?: RuntimeEvent): Promise<string>;
|
|
75
|
+
fence(): void;
|
|
76
|
+
/** The trusted owner must verify ALL execution resources first. A model exit,
|
|
77
|
+
* timeout or user stop is never accepted as proof. Failed acknowledgement is
|
|
78
|
+
* retryable on this same object/attempt; no new claim or replay is created. */
|
|
79
|
+
acknowledgeStopped(reason: Reason, verifyStopped: () => Promise<void>): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EntityboxExecutionLease = exports.EntityboxExecutionError = void 0;
|
|
37
|
+
const crypto_1 = require("crypto");
|
|
38
|
+
const entitybox_file_inputs_1 = require("./entitybox-file-inputs");
|
|
39
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
40
|
+
const entitybox_mcp_contract_1 = require("./entitybox-mcp-contract");
|
|
41
|
+
class EntityboxExecutionError extends Error {
|
|
42
|
+
}
|
|
43
|
+
exports.EntityboxExecutionError = EntityboxExecutionError;
|
|
44
|
+
/** Runtime-only authority. Neither pairing nor the lease nonce is exported to
|
|
45
|
+
* the model/container. The SQL contract grants 15 seconds from execution of a
|
|
46
|
+
* fresh RPC. Our monotonic deadline starts BEFORE that RPC and reserves 2s.
|
|
47
|
+
* Claim is never automatically retried: its idempotent response cannot be used
|
|
48
|
+
* as a fresh 15-second grant. Container challenges always require a renewal. */
|
|
49
|
+
class EntityboxExecutionLease {
|
|
50
|
+
token;
|
|
51
|
+
rpc;
|
|
52
|
+
identity;
|
|
53
|
+
signal;
|
|
54
|
+
abort = new AbortController();
|
|
55
|
+
nonce = (0, crypto_1.randomUUID)();
|
|
56
|
+
deadline = 0;
|
|
57
|
+
timer;
|
|
58
|
+
epoch;
|
|
59
|
+
renewing = false;
|
|
60
|
+
challengeQueue = Promise.resolve();
|
|
61
|
+
queuedChallenges = 0;
|
|
62
|
+
acknowledged = false;
|
|
63
|
+
claimed = false;
|
|
64
|
+
provider;
|
|
65
|
+
inventorySealed = false;
|
|
66
|
+
constructor(requestId, scope, token, rpc) {
|
|
67
|
+
this.token = token;
|
|
68
|
+
this.rpc = rpc;
|
|
69
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(requestId))
|
|
70
|
+
throw new EntityboxExecutionError('Invalid computer request');
|
|
71
|
+
this.identity = Object.freeze({ requestId: requestId.toLowerCase(), attemptId: (0, crypto_1.randomUUID)(), scope: (0, entitybox_mcp_contract_1.parseEntityboxMcpScope)(scope) });
|
|
72
|
+
this.signal = this.abort.signal;
|
|
73
|
+
}
|
|
74
|
+
static async claim(requestId, scope, token, rpc) {
|
|
75
|
+
const lease = new EntityboxExecutionLease(requestId, scope, token, rpc);
|
|
76
|
+
const started = perf_hooks_1.performance.now();
|
|
77
|
+
try {
|
|
78
|
+
const value = await lease.call('claim', { p_request_id: lease.identity.requestId });
|
|
79
|
+
const receipt = lease.receipt(value);
|
|
80
|
+
if (receipt.phase !== 'running')
|
|
81
|
+
throw new Error();
|
|
82
|
+
lease.epoch = receipt.epoch;
|
|
83
|
+
lease.claimed = true;
|
|
84
|
+
lease.arm(started);
|
|
85
|
+
return lease;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
lease.fence();
|
|
89
|
+
// A lost/malformed reply may still have committed. No executor was
|
|
90
|
+
// started, but do not guess at a receipt or retry with another attempt.
|
|
91
|
+
throw new EntityboxExecutionError('Computer execution could not obtain a verified lease');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async call(name, extra = {}) {
|
|
95
|
+
let timer;
|
|
96
|
+
try {
|
|
97
|
+
return await Promise.race([
|
|
98
|
+
this.rpc('entitybox_native_execution_' + name, {
|
|
99
|
+
p_token: this.token(), p_attempt_id: this.identity.attemptId, p_lease_token: this.nonce, ...extra,
|
|
100
|
+
}),
|
|
101
|
+
new Promise((_, reject) => { timer = setTimeout(() => reject(new EntityboxExecutionError('Execution RPC deadline ended')), 4500); }),
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
receipt(value) {
|
|
109
|
+
const r = value;
|
|
110
|
+
const scope = (0, entitybox_mcp_contract_1.parseEntityboxMcpScope)(r?.scope);
|
|
111
|
+
if (!r || r.attemptId !== this.identity.attemptId || r.requestId !== this.identity.requestId
|
|
112
|
+
|| r.runId !== this.identity.scope.runId || !Number.isSafeInteger(r.epoch) || r.epoch < 1
|
|
113
|
+
|| (this.epoch !== undefined && r.epoch !== this.epoch)
|
|
114
|
+
|| !['running', 'stop_requested', 'stopped'].includes(r.phase)
|
|
115
|
+
|| typeof r.leaseExpiresAt !== 'string' || !Number.isFinite(Date.parse(r.leaseExpiresAt))
|
|
116
|
+
|| scope.runId !== this.identity.scope.runId || scope.desktopId !== this.identity.scope.desktopId || scope.runtimeId !== this.identity.scope.runtimeId)
|
|
117
|
+
throw new Error();
|
|
118
|
+
return r;
|
|
119
|
+
}
|
|
120
|
+
arm(started) {
|
|
121
|
+
const deadline = started + 13_000;
|
|
122
|
+
if (this.signal.aborted || perf_hooks_1.performance.now() >= deadline) {
|
|
123
|
+
this.fence();
|
|
124
|
+
throw new Error();
|
|
125
|
+
}
|
|
126
|
+
this.deadline = deadline;
|
|
127
|
+
clearTimeout(this.timer);
|
|
128
|
+
this.timer = setTimeout(() => this.fence(), Math.max(1, deadline - perf_hooks_1.performance.now()));
|
|
129
|
+
}
|
|
130
|
+
assertActive() {
|
|
131
|
+
if (!this.claimed || this.signal.aborted || perf_hooks_1.performance.now() >= this.deadline) {
|
|
132
|
+
this.fence();
|
|
133
|
+
throw new EntityboxExecutionError('Computer execution lease ended');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** A watchdog issues its own challenge BEFORE this method calls the server.
|
|
137
|
+
* It accepts the response only within its original challenge deadline, so a
|
|
138
|
+
* buffered/delayed grant can never restart or prolong an expired container. */
|
|
139
|
+
/** Independent model/application guards each require a fresh renewal. Their
|
|
140
|
+
* original challenge clocks still bound queued replies; no grant is shared. */
|
|
141
|
+
authorizeQueuedChallenge() {
|
|
142
|
+
if (this.queuedChallenges >= 4) {
|
|
143
|
+
this.fence();
|
|
144
|
+
return Promise.reject(new EntityboxExecutionError('Execution challenge queue exceeded'));
|
|
145
|
+
}
|
|
146
|
+
this.queuedChallenges++;
|
|
147
|
+
const work = this.challengeQueue.then(() => { this.assertActive(); return this.authorizeChallenge(); });
|
|
148
|
+
this.challengeQueue = work.catch(() => { }).finally(() => { this.queuedChallenges--; });
|
|
149
|
+
return work;
|
|
150
|
+
}
|
|
151
|
+
async authorizeChallenge() {
|
|
152
|
+
this.assertActive();
|
|
153
|
+
if (this.renewing) {
|
|
154
|
+
this.fence();
|
|
155
|
+
throw new EntityboxExecutionError('Overlapping execution renewal');
|
|
156
|
+
}
|
|
157
|
+
this.renewing = true;
|
|
158
|
+
const started = perf_hooks_1.performance.now();
|
|
159
|
+
try {
|
|
160
|
+
const r = this.receipt(await this.call('renew'));
|
|
161
|
+
this.assertActive();
|
|
162
|
+
if (r.phase !== 'running')
|
|
163
|
+
throw new Error();
|
|
164
|
+
this.arm(started);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
this.fence();
|
|
168
|
+
throw new EntityboxExecutionError('Computer execution renewal ended');
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
this.renewing = false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Events remain host-authenticated and server-checked. The model never
|
|
175
|
+
* receives the lease nonce. Queued output may drain after local process
|
|
176
|
+
* stop, but not after server expiry, cancellation or stop acknowledgement. */
|
|
177
|
+
async appendEvent(event) {
|
|
178
|
+
if (!this.claimed || this.acknowledged || event.type === 'turn_complete')
|
|
179
|
+
throw new EntityboxExecutionError('Execution event unavailable');
|
|
180
|
+
const result = await this.call('event', { p_event: event });
|
|
181
|
+
if (typeof result !== 'boolean')
|
|
182
|
+
throw new EntityboxExecutionError('Execution event was not confirmed');
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
/** Metadata only: the native server rechecks the immutable request, current
|
|
186
|
+
* provider binding and active lease. This never renews the execution lease
|
|
187
|
+
* or returns a secret. A changed selection fences this attempt. */
|
|
188
|
+
async providerSelection() {
|
|
189
|
+
this.assertActive();
|
|
190
|
+
try {
|
|
191
|
+
const value = await this.call('provider');
|
|
192
|
+
const receipt = this.receipt(value), selection = value.provider;
|
|
193
|
+
this.assertActive();
|
|
194
|
+
if (receipt.phase !== 'running' || !selection || selection.kind !== 'anthropic_api_key'
|
|
195
|
+
|| typeof selection.vaultItemId !== 'string' || !/^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(selection.vaultItemId)
|
|
196
|
+
|| typeof selection.model !== 'string' || selection.model.length > 120 || !/^claude-[a-zA-Z0-9_.:-]+$/.test(selection.model)
|
|
197
|
+
|| Object.keys(selection).sort().join(',') !== 'kind,model,vaultItemId'
|
|
198
|
+
|| (this.provider && (this.provider.vaultItemId !== selection.vaultItemId || this.provider.model !== selection.model)))
|
|
199
|
+
throw new Error();
|
|
200
|
+
this.provider = Object.freeze({ ...selection });
|
|
201
|
+
return this.provider;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
this.fence();
|
|
205
|
+
throw new EntityboxExecutionError('The computer provider selection could not be verified');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** The nonce/pairing remain private to this host closure. Downloads happen
|
|
209
|
+
* only while the independent execution watchdog is renewing the lease. */
|
|
210
|
+
fileInputs(gatewayUrl, transport = fetch) {
|
|
211
|
+
const url = (0, entitybox_file_inputs_1.entityboxFileGatewayUrl)(gatewayUrl);
|
|
212
|
+
return new entitybox_file_inputs_1.EntityboxFileInputs({ requestId: this.identity.requestId, signal: this.signal, requireFiles: true,
|
|
213
|
+
manifest: async (signal) => {
|
|
214
|
+
this.assertActive();
|
|
215
|
+
if (signal.aborted)
|
|
216
|
+
throw new Error();
|
|
217
|
+
const result = await this.rpc('entitybox_file_manifest', { p_token: this.token(), p_request_id: this.identity.requestId, p_attempt_id: this.identity.attemptId, p_lease_token: this.nonce });
|
|
218
|
+
this.assertActive();
|
|
219
|
+
if (signal.aborted)
|
|
220
|
+
throw new Error();
|
|
221
|
+
return result;
|
|
222
|
+
},
|
|
223
|
+
download: async (fileId, signal) => {
|
|
224
|
+
this.assertActive();
|
|
225
|
+
if (signal.aborted)
|
|
226
|
+
throw new Error();
|
|
227
|
+
const response = await transport(url, { method: 'POST', redirect: 'error', credentials: 'omit', signal,
|
|
228
|
+
headers: { authorization: 'Bearer ' + this.token(), 'content-type': 'application/json' },
|
|
229
|
+
body: JSON.stringify({ requestId: this.identity.requestId, fileId, attemptId: this.identity.attemptId, leaseToken: this.nonce }) });
|
|
230
|
+
try {
|
|
231
|
+
this.assertActive();
|
|
232
|
+
if (signal.aborted)
|
|
233
|
+
throw new Error();
|
|
234
|
+
return response;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
void response.body?.cancel().catch(() => { });
|
|
238
|
+
throw new Error();
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/** Public immutable resource identity only. Registration happens before a
|
|
244
|
+
* guarded application resumes or a newly created executor starts. These RPCs
|
|
245
|
+
* never renew authority, and their receipts are checked against this lease. */
|
|
246
|
+
async registerResource(kind, binding) {
|
|
247
|
+
this.assertActive();
|
|
248
|
+
try {
|
|
249
|
+
const copy = JSON.parse(JSON.stringify(binding)), canonical = (value) => JSON.stringify(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)));
|
|
250
|
+
if (!['application', 'display', 'execution'].includes(kind) || !copy || typeof copy !== 'object' || Array.isArray(copy) || Buffer.byteLength(JSON.stringify(copy)) > 2048 || !Object.values(copy).every(v => typeof v === 'string'))
|
|
251
|
+
throw new Error();
|
|
252
|
+
const value = await this.call('register', { p_kind: kind, p_binding: copy });
|
|
253
|
+
const receipt = this.receipt(value);
|
|
254
|
+
this.assertActive();
|
|
255
|
+
if (receipt.phase !== 'running' || value.resource?.kind !== kind || !value.resource.binding || canonical(value.resource.binding) !== canonical(copy))
|
|
256
|
+
throw new Error();
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
this.fence();
|
|
260
|
+
throw new EntityboxExecutionError('Execution resource registration was not confirmed');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async registerRecoveryHost(host) {
|
|
264
|
+
this.assertActive();
|
|
265
|
+
try {
|
|
266
|
+
const { parseEntityboxRecoveryHost, entityboxCanonical } = await Promise.resolve().then(() => __importStar(require('./entitybox-recovery-store')));
|
|
267
|
+
this.assertActive();
|
|
268
|
+
const copy = parseEntityboxRecoveryHost(host), value = await this.call('host', { p_host: copy });
|
|
269
|
+
const receipt = this.receipt(value);
|
|
270
|
+
this.assertActive();
|
|
271
|
+
if (receipt.phase !== 'running' || entityboxCanonical(value.host) !== entityboxCanonical(copy))
|
|
272
|
+
throw new Error();
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
this.fence();
|
|
276
|
+
throw new EntityboxExecutionError('Execution recovery host was not confirmed');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async sealInventory(hasExecution) {
|
|
280
|
+
this.assertActive();
|
|
281
|
+
try {
|
|
282
|
+
if (typeof hasExecution !== 'boolean')
|
|
283
|
+
throw new Error();
|
|
284
|
+
const value = await this.call('seal', { p_has_execution: hasExecution });
|
|
285
|
+
const receipt = this.receipt(value);
|
|
286
|
+
this.assertActive();
|
|
287
|
+
if (receipt.phase !== 'running' || value.inventory?.sealed !== true || value.inventory.hasExecution !== hasExecution)
|
|
288
|
+
throw new Error();
|
|
289
|
+
this.inventorySealed = true;
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
this.fence();
|
|
293
|
+
throw new EntityboxExecutionError('Execution inventory was not confirmed');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
assertInventorySealed() { this.assertActive(); if (!this.inventorySealed)
|
|
297
|
+
throw new EntityboxExecutionError('Execution inventory is incomplete'); }
|
|
298
|
+
/** Atomic native event/status commit preserves cancellation and prevents
|
|
299
|
+
* duplicate terminal events after a lost response. Requires physical proof. */
|
|
300
|
+
async finishRequest(outcome, completion) {
|
|
301
|
+
if (!this.acknowledged)
|
|
302
|
+
throw new EntityboxExecutionError('Execution stop has not been confirmed');
|
|
303
|
+
const value = await this.call('finish', { p_outcome: outcome, p_completion: completion ?? null });
|
|
304
|
+
if (value?.requestId !== this.identity.requestId || !['completed', 'failed', 'canceled', 'dead_letter', 'deleted'].includes(value?.status ?? ''))
|
|
305
|
+
throw new EntityboxExecutionError('Execution final status was not confirmed');
|
|
306
|
+
return value.status;
|
|
307
|
+
}
|
|
308
|
+
fence() {
|
|
309
|
+
clearTimeout(this.timer);
|
|
310
|
+
if (!this.signal.aborted)
|
|
311
|
+
this.abort.abort(new EntityboxExecutionError('Computer execution fenced'));
|
|
312
|
+
}
|
|
313
|
+
/** The trusted owner must verify ALL execution resources first. A model exit,
|
|
314
|
+
* timeout or user stop is never accepted as proof. Failed acknowledgement is
|
|
315
|
+
* retryable on this same object/attempt; no new claim or replay is created. */
|
|
316
|
+
async acknowledgeStopped(reason, verifyStopped) {
|
|
317
|
+
this.fence();
|
|
318
|
+
if (this.acknowledged)
|
|
319
|
+
return;
|
|
320
|
+
try {
|
|
321
|
+
await verifyStopped();
|
|
322
|
+
const r = this.receipt(await this.call('stopped', { p_reason: reason }));
|
|
323
|
+
if (r.phase !== 'stopped')
|
|
324
|
+
throw new Error();
|
|
325
|
+
this.acknowledged = true;
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
throw new EntityboxExecutionError('Computer stop could not be confirmed');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
exports.EntityboxExecutionLease = EntityboxExecutionLease;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface EntityboxInputFile {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
size: number;
|
|
6
|
+
sha256: string;
|
|
7
|
+
}
|
|
8
|
+
export interface EntityboxFileAuthority {
|
|
9
|
+
requestId: string;
|
|
10
|
+
signal: AbortSignal;
|
|
11
|
+
requireFiles?: boolean;
|
|
12
|
+
manifest: (signal: AbortSignal) => Promise<unknown>;
|
|
13
|
+
download: (fileId: string, signal: AbortSignal) => Promise<Response>;
|
|
14
|
+
}
|
|
15
|
+
export type EntityboxFileBlock = {
|
|
16
|
+
type: 'text';
|
|
17
|
+
text: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'image' | 'document';
|
|
20
|
+
source: {
|
|
21
|
+
type: 'base64';
|
|
22
|
+
media_type: string;
|
|
23
|
+
data: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
type Block = EntityboxFileBlock;
|
|
27
|
+
export declare const ENTITYBOX_FILE_TOTAL_BYTES: number;
|
|
28
|
+
export declare const ENTITYBOX_IMAGE_BYTES: number;
|
|
29
|
+
export declare const ENTITYBOX_PROVIDER_BYTES: number;
|
|
30
|
+
export declare class EntityboxFileInputError extends Error {
|
|
31
|
+
constructor();
|
|
32
|
+
}
|
|
33
|
+
/** Only a trusted host setting may name the gateway. Models, files and queued
|
|
34
|
+
* prompts never choose a URL. Redirects and ambient credentials are disabled. */
|
|
35
|
+
export declare function entityboxFileGatewayUrl(value: string): string;
|
|
36
|
+
export declare function entityboxFileManifest(value: unknown, requestId: string): readonly Readonly<EntityboxInputFile>[];
|
|
37
|
+
/** Task-scoped original bytes. No URLs, credentials, binary IPC, host paths or
|
|
38
|
+
* durable cache reach the isolated executor. Authorization is repeated for
|
|
39
|
+
* every inference, including count_tokens and retries, and after all downloads.
|
|
40
|
+
* The random anchor binds files to this task's user message, not warm-up calls
|
|
41
|
+
* or tool results. The model gets ordinary image/document content blocks. */
|
|
42
|
+
export declare class EntityboxFileInputs {
|
|
43
|
+
private readonly authority;
|
|
44
|
+
private readonly anchor;
|
|
45
|
+
private expectedManifest?;
|
|
46
|
+
constructor(authority: EntityboxFileAuthority);
|
|
47
|
+
prompt(text: string): string;
|
|
48
|
+
blocks(signal: AbortSignal): Promise<readonly Block[]>;
|
|
49
|
+
attach(body: Record<string, unknown>, signal: AbortSignal): Promise<boolean>;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EntityboxFileInputs = exports.EntityboxFileInputError = exports.ENTITYBOX_PROVIDER_BYTES = exports.ENTITYBOX_IMAGE_BYTES = exports.ENTITYBOX_FILE_TOTAL_BYTES = void 0;
|
|
4
|
+
exports.entityboxFileGatewayUrl = entityboxFileGatewayUrl;
|
|
5
|
+
exports.entityboxFileManifest = entityboxFileManifest;
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
const entitybox_operation_1 = require("./entitybox-operation");
|
|
8
|
+
const uuid = /^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/;
|
|
9
|
+
const formats = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif', 'application/pdf']);
|
|
10
|
+
// Deliberately below the provider's 32 MB request and 10 MB encoded-image
|
|
11
|
+
// limits. Reserve room for the CLI's system prompt, history and tools.
|
|
12
|
+
exports.ENTITYBOX_FILE_TOTAL_BYTES = 18 * 1024 * 1024;
|
|
13
|
+
exports.ENTITYBOX_IMAGE_BYTES = 7 * 1024 * 1024;
|
|
14
|
+
exports.ENTITYBOX_PROVIDER_BYTES = 30 * 1024 * 1024;
|
|
15
|
+
class EntityboxFileInputError extends Error {
|
|
16
|
+
constructor() { super('An attachment is unavailable or cannot be processed. Reattach a smaller PNG, JPEG, WebP, GIF or unlocked PDF and try again.'); }
|
|
17
|
+
}
|
|
18
|
+
exports.EntityboxFileInputError = EntityboxFileInputError;
|
|
19
|
+
const fail = () => new EntityboxFileInputError();
|
|
20
|
+
const exact = (v, keys) => Object.keys(v).sort().join(',') === keys.sort().join(',');
|
|
21
|
+
/** Only a trusted host setting may name the gateway. Models, files and queued
|
|
22
|
+
* prompts never choose a URL. Redirects and ambient credentials are disabled. */
|
|
23
|
+
function entityboxFileGatewayUrl(value) {
|
|
24
|
+
try {
|
|
25
|
+
const u = new URL(value);
|
|
26
|
+
if (u.protocol !== 'https:' || u.username || u.password || u.hash || u.search || u.port || u.href !== value || u.pathname === '/' || u.hostname === 'localhost')
|
|
27
|
+
throw fail();
|
|
28
|
+
return u.href;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw fail();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function entityboxFileManifest(value, requestId) {
|
|
35
|
+
const m = value;
|
|
36
|
+
if (!m || !exact(m, ['version', 'requestId', 'files']) || m.version !== 1 || m.requestId !== requestId || !uuid.test(requestId) || !Array.isArray(m.files) || m.files.length > 6)
|
|
37
|
+
throw fail();
|
|
38
|
+
const ids = new Set();
|
|
39
|
+
let size = 0;
|
|
40
|
+
const files = m.files.map((raw) => {
|
|
41
|
+
const f = raw;
|
|
42
|
+
if (!f || !exact(f, ['id', 'name', 'type', 'size', 'sha256']) || typeof f.id !== 'string' || !uuid.test(f.id) || ids.has(f.id)
|
|
43
|
+
|| typeof f.name !== 'string' || !f.name.trim() || f.name.length > 180 || /[\u0000-\u001f\u007f]/.test(f.name)
|
|
44
|
+
|| !formats.has(f.type) || !Number.isSafeInteger(f.size) || f.size < 1 || f.size > exports.ENTITYBOX_FILE_TOTAL_BYTES
|
|
45
|
+
|| f.type !== 'application/pdf' && f.size > exports.ENTITYBOX_IMAGE_BYTES || typeof f.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(f.sha256))
|
|
46
|
+
throw fail();
|
|
47
|
+
size += f.size;
|
|
48
|
+
if (size > exports.ENTITYBOX_FILE_TOTAL_BYTES)
|
|
49
|
+
throw fail();
|
|
50
|
+
ids.add(f.id);
|
|
51
|
+
return Object.freeze({ id: f.id, name: f.name, type: f.type, size: f.size, sha256: f.sha256 });
|
|
52
|
+
});
|
|
53
|
+
return Object.freeze(files);
|
|
54
|
+
}
|
|
55
|
+
/** This is a format-signature check, not a decoder. The provider performs
|
|
56
|
+
* image/PDF decoding; malformed or encrypted documents must remain errors. */
|
|
57
|
+
function signature(bytes, type) {
|
|
58
|
+
if (type === 'image/png')
|
|
59
|
+
return bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
|
|
60
|
+
if (type === 'image/jpeg')
|
|
61
|
+
return bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
|
|
62
|
+
if (type === 'image/gif')
|
|
63
|
+
return ['GIF87a', 'GIF89a'].includes(bytes.subarray(0, 6).toString('ascii'));
|
|
64
|
+
if (type === 'image/webp')
|
|
65
|
+
return bytes.subarray(0, 4).toString('ascii') === 'RIFF' && bytes.subarray(8, 12).toString('ascii') === 'WEBP';
|
|
66
|
+
return bytes.subarray(0, 5).toString('ascii') === '%PDF-';
|
|
67
|
+
}
|
|
68
|
+
async function readFile(response, file, signal) {
|
|
69
|
+
let reader;
|
|
70
|
+
const stop = () => { void reader?.cancel().catch(() => { }); };
|
|
71
|
+
try {
|
|
72
|
+
const length = response.headers.get('content-length');
|
|
73
|
+
if (signal.aborted || response.status !== 200 || response.redirected || !response.body
|
|
74
|
+
|| response.headers.get('content-type')?.split(';')[0] !== file.type
|
|
75
|
+
|| response.headers.get('x-entitybox-sha256') !== file.sha256
|
|
76
|
+
|| length !== null && (!/^\d+$/.test(length) || Number(length) !== file.size))
|
|
77
|
+
throw fail();
|
|
78
|
+
reader = response.body.getReader();
|
|
79
|
+
signal.addEventListener('abort', stop, { once: true });
|
|
80
|
+
const chunks = [];
|
|
81
|
+
let size = 0;
|
|
82
|
+
while (true) {
|
|
83
|
+
const item = await reader.read();
|
|
84
|
+
if (signal.aborted)
|
|
85
|
+
throw fail();
|
|
86
|
+
if (item.done)
|
|
87
|
+
break;
|
|
88
|
+
size += item.value.byteLength;
|
|
89
|
+
if (size > file.size)
|
|
90
|
+
throw fail();
|
|
91
|
+
chunks.push(Buffer.from(item.value));
|
|
92
|
+
}
|
|
93
|
+
const bytes = Buffer.concat(chunks);
|
|
94
|
+
if (size !== file.size || (0, crypto_1.createHash)('sha256').update(bytes).digest('hex') !== file.sha256 || !signature(bytes, file.type))
|
|
95
|
+
throw fail();
|
|
96
|
+
return bytes;
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
signal.removeEventListener('abort', stop);
|
|
100
|
+
if (reader) {
|
|
101
|
+
await reader.cancel().catch(() => { });
|
|
102
|
+
reader.releaseLock();
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
await response.body?.cancel().catch(() => { });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Task-scoped original bytes. No URLs, credentials, binary IPC, host paths or
|
|
109
|
+
* durable cache reach the isolated executor. Authorization is repeated for
|
|
110
|
+
* every inference, including count_tokens and retries, and after all downloads.
|
|
111
|
+
* The random anchor binds files to this task's user message, not warm-up calls
|
|
112
|
+
* or tool results. The model gets ordinary image/document content blocks. */
|
|
113
|
+
class EntityboxFileInputs {
|
|
114
|
+
authority;
|
|
115
|
+
anchor = `[EntityBox task files ${(0, crypto_1.randomUUID)()}]`;
|
|
116
|
+
expectedManifest;
|
|
117
|
+
constructor(authority) {
|
|
118
|
+
this.authority = authority;
|
|
119
|
+
if (!uuid.test(authority.requestId))
|
|
120
|
+
throw fail();
|
|
121
|
+
}
|
|
122
|
+
prompt(text) { return text + '\n\n' + this.anchor; }
|
|
123
|
+
async blocks(signal) {
|
|
124
|
+
try {
|
|
125
|
+
return await (0, entitybox_operation_1.entityboxOperation)([this.authority.signal, signal], 90000, async (active) => {
|
|
126
|
+
const files = entityboxFileManifest(await this.authority.manifest(active), this.authority.requestId);
|
|
127
|
+
if (this.authority.requireFiles && !files.length)
|
|
128
|
+
throw fail();
|
|
129
|
+
const bound = JSON.stringify(files);
|
|
130
|
+
if (this.expectedManifest !== undefined && this.expectedManifest !== bound)
|
|
131
|
+
throw fail();
|
|
132
|
+
this.expectedManifest = bound;
|
|
133
|
+
const blocks = [];
|
|
134
|
+
for (const file of files) {
|
|
135
|
+
const bytes = await (0, entitybox_operation_1.entityboxOperation)([active], 30000, async (current) => {
|
|
136
|
+
const response = await this.authority.download(file.id, current);
|
|
137
|
+
return readFile(response, file, current);
|
|
138
|
+
});
|
|
139
|
+
blocks.push({ type: 'text', text: `Attached file: ${JSON.stringify(file.name)}` }, { type: file.type === 'application/pdf' ? 'document' : 'image', source: { type: 'base64', media_type: file.type, data: bytes.toString('base64') } });
|
|
140
|
+
}
|
|
141
|
+
const current = entityboxFileManifest(await this.authority.manifest(active), this.authority.requestId);
|
|
142
|
+
if (JSON.stringify(files) !== JSON.stringify(current) || active.aborted)
|
|
143
|
+
throw fail();
|
|
144
|
+
return blocks;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
throw fail();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async attach(body, signal) {
|
|
152
|
+
if (!Array.isArray(body.messages))
|
|
153
|
+
throw fail();
|
|
154
|
+
let target;
|
|
155
|
+
let occurrences = 0;
|
|
156
|
+
for (const message of body.messages) {
|
|
157
|
+
if (!message || message.role !== 'user')
|
|
158
|
+
continue;
|
|
159
|
+
const content = typeof message.content === 'string' ? [{ type: 'text', text: message.content }] : message.content;
|
|
160
|
+
if (!Array.isArray(content))
|
|
161
|
+
continue;
|
|
162
|
+
for (const block of content) {
|
|
163
|
+
if (block?.type !== 'text' || typeof block.text !== 'string')
|
|
164
|
+
continue;
|
|
165
|
+
const count = block.text.split(this.anchor).length - 1;
|
|
166
|
+
if (count) {
|
|
167
|
+
occurrences += count;
|
|
168
|
+
target = { message, content };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!occurrences)
|
|
173
|
+
return false;
|
|
174
|
+
if (occurrences !== 1 || !target)
|
|
175
|
+
throw fail();
|
|
176
|
+
const blocks = await this.blocks(signal);
|
|
177
|
+
const text = target.content.map(b => b.type === 'text' && typeof b.text === 'string' ? { ...b, text: b.text.replace(this.anchor, '').trimEnd() } : b);
|
|
178
|
+
target.message.content = [...blocks, ...text];
|
|
179
|
+
return blocks.length > 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.EntityboxFileInputs = EntityboxFileInputs;
|