@realtimex/sdk 1.3.4 → 1.3.5-rc.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 +61 -0
- package/dist/index.d.mts +646 -7
- package/dist/index.d.ts +646 -7
- package/dist/index.js +1899 -31
- package/dist/index.mjs +1859 -30
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -30,22 +30,61 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
ACPContractAdapter: () => ACPContractAdapter,
|
|
34
|
+
ACPEventMapper: () => ACPEventMapper,
|
|
35
|
+
ACPPermissionBridge: () => ACPPermissionBridge,
|
|
36
|
+
ACPTelemetry: () => ACPTelemetry,
|
|
33
37
|
ActivitiesModule: () => ActivitiesModule,
|
|
34
38
|
AgentModule: () => AgentModule,
|
|
35
39
|
ApiModule: () => ApiModule,
|
|
40
|
+
CONTRACT_ATTEMPT_PREFIX: () => CONTRACT_ATTEMPT_PREFIX,
|
|
41
|
+
CONTRACT_EVENT_ID_HEADER: () => CONTRACT_EVENT_ID_HEADER,
|
|
42
|
+
CONTRACT_SIGNATURE_ALGORITHM: () => CONTRACT_SIGNATURE_ALGORITHM,
|
|
43
|
+
CONTRACT_SIGNATURE_HEADER: () => CONTRACT_SIGNATURE_HEADER,
|
|
44
|
+
ClaudeToolAdapter: () => ClaudeToolAdapter,
|
|
45
|
+
CodexToolAdapter: () => CodexToolAdapter,
|
|
46
|
+
ContractCache: () => ContractCache,
|
|
47
|
+
ContractClient: () => ContractClient,
|
|
48
|
+
ContractError: () => ContractError,
|
|
49
|
+
ContractHttpClient: () => ContractHttpClient,
|
|
50
|
+
ContractModule: () => ContractModule,
|
|
51
|
+
ContractRuntime: () => ContractRuntime,
|
|
52
|
+
ContractValidationError: () => ContractValidationError,
|
|
53
|
+
GeminiToolAdapter: () => GeminiToolAdapter,
|
|
36
54
|
LLMModule: () => LLMModule,
|
|
37
55
|
LLMPermissionError: () => LLMPermissionError,
|
|
38
56
|
LLMProviderError: () => LLMProviderError,
|
|
57
|
+
LOCAL_APP_CONTRACT_VERSION: () => LOCAL_APP_CONTRACT_VERSION,
|
|
39
58
|
MCPModule: () => MCPModule,
|
|
40
59
|
PermissionDeniedError: () => PermissionDeniedError,
|
|
41
60
|
PermissionRequiredError: () => PermissionRequiredError,
|
|
42
61
|
PortModule: () => PortModule,
|
|
43
62
|
RealtimeXSDK: () => RealtimeXSDK,
|
|
63
|
+
RetryPolicy: () => RetryPolicy,
|
|
64
|
+
RuntimeTransportError: () => RuntimeTransportError,
|
|
44
65
|
STTModule: () => STTModule,
|
|
66
|
+
ScopeDeniedError: () => ScopeDeniedError,
|
|
67
|
+
ScopeGuard: () => ScopeGuard,
|
|
68
|
+
StaticAuthProvider: () => StaticAuthProvider,
|
|
45
69
|
TTSModule: () => TTSModule,
|
|
46
70
|
TaskModule: () => TaskModule,
|
|
71
|
+
ToolNotFoundError: () => ToolNotFoundError,
|
|
72
|
+
ToolProjector: () => ToolProjector,
|
|
73
|
+
ToolValidationError: () => ToolValidationError,
|
|
47
74
|
VectorStore: () => VectorStore,
|
|
48
|
-
WebhookModule: () => WebhookModule
|
|
75
|
+
WebhookModule: () => WebhookModule,
|
|
76
|
+
buildContractIdempotencyKey: () => buildContractIdempotencyKey,
|
|
77
|
+
buildContractSignatureMessage: () => buildContractSignatureMessage,
|
|
78
|
+
canonicalEventToLegacyAction: () => canonicalEventToLegacyAction,
|
|
79
|
+
createContractEventId: () => createContractEventId,
|
|
80
|
+
hashContractPayload: () => hashContractPayload,
|
|
81
|
+
normalizeAttemptId: () => normalizeAttemptId,
|
|
82
|
+
normalizeContractEvent: () => normalizeContractEvent,
|
|
83
|
+
normalizeLocalAppContractV1: () => normalizeLocalAppContractV1,
|
|
84
|
+
normalizeSchema: () => normalizeSchema,
|
|
85
|
+
parseAttemptRunId: () => parseAttemptRunId,
|
|
86
|
+
signContractEvent: () => signContractEvent,
|
|
87
|
+
toStableToolName: () => toStableToolName
|
|
49
88
|
});
|
|
50
89
|
module.exports = __toCommonJS(index_exports);
|
|
51
90
|
|
|
@@ -279,6 +318,204 @@ var ActivitiesModule = class {
|
|
|
279
318
|
}
|
|
280
319
|
};
|
|
281
320
|
|
|
321
|
+
// src/modules/contract.ts
|
|
322
|
+
var import_crypto = require("crypto");
|
|
323
|
+
var LOCAL_APP_CONTRACT_VERSION = "local-app-contract/v1";
|
|
324
|
+
var CONTRACT_SIGNATURE_HEADER = "x-rtx-contract-signature";
|
|
325
|
+
var CONTRACT_EVENT_ID_HEADER = "x-rtx-event-id";
|
|
326
|
+
var CONTRACT_SIGNATURE_ALGORITHM = "sha256";
|
|
327
|
+
var CONTRACT_ATTEMPT_PREFIX = "run-";
|
|
328
|
+
var CONTRACT_EVENT_ALIASES = {
|
|
329
|
+
"trigger-agent": "task.trigger",
|
|
330
|
+
"task.trigger": "task.trigger",
|
|
331
|
+
ping: "system.ping",
|
|
332
|
+
"system.ping": "system.ping",
|
|
333
|
+
claim: "task.claimed",
|
|
334
|
+
claimed: "task.claimed",
|
|
335
|
+
"task.claimed": "task.claimed",
|
|
336
|
+
"task-start": "task.started",
|
|
337
|
+
start: "task.started",
|
|
338
|
+
"task.started": "task.started",
|
|
339
|
+
"task-progress": "task.progress",
|
|
340
|
+
progress: "task.progress",
|
|
341
|
+
processing: "task.progress",
|
|
342
|
+
"task.progress": "task.progress",
|
|
343
|
+
"task-complete": "task.completed",
|
|
344
|
+
complete: "task.completed",
|
|
345
|
+
completed: "task.completed",
|
|
346
|
+
"task.completed": "task.completed",
|
|
347
|
+
"task-fail": "task.failed",
|
|
348
|
+
fail: "task.failed",
|
|
349
|
+
failed: "task.failed",
|
|
350
|
+
"task.failed": "task.failed",
|
|
351
|
+
"task-cancel": "task.canceled",
|
|
352
|
+
"task-cancelled": "task.canceled",
|
|
353
|
+
"task-canceled": "task.canceled",
|
|
354
|
+
cancel: "task.canceled",
|
|
355
|
+
cancelled: "task.canceled",
|
|
356
|
+
canceled: "task.canceled",
|
|
357
|
+
"task.canceled": "task.canceled"
|
|
358
|
+
};
|
|
359
|
+
var CONTRACT_LEGACY_ACTIONS = {
|
|
360
|
+
"task.trigger": "trigger-agent",
|
|
361
|
+
"system.ping": "ping",
|
|
362
|
+
"task.claimed": "claim",
|
|
363
|
+
"task.started": "start",
|
|
364
|
+
"task.progress": "progress",
|
|
365
|
+
"task.completed": "complete",
|
|
366
|
+
"task.failed": "fail",
|
|
367
|
+
"task.canceled": "cancel"
|
|
368
|
+
};
|
|
369
|
+
function normalizeContractEvent(eventLike) {
|
|
370
|
+
if (!eventLike || typeof eventLike !== "string") return null;
|
|
371
|
+
const normalized = CONTRACT_EVENT_ALIASES[eventLike.trim().toLowerCase()];
|
|
372
|
+
return normalized || null;
|
|
373
|
+
}
|
|
374
|
+
function normalizeAttemptId(attemptLike) {
|
|
375
|
+
if (attemptLike === null || attemptLike === void 0) return void 0;
|
|
376
|
+
if (typeof attemptLike === "number" && Number.isInteger(attemptLike) && attemptLike > 0) {
|
|
377
|
+
return `${CONTRACT_ATTEMPT_PREFIX}${attemptLike}`;
|
|
378
|
+
}
|
|
379
|
+
if (typeof attemptLike !== "string") return void 0;
|
|
380
|
+
const trimmed = attemptLike.trim();
|
|
381
|
+
if (!trimmed) return void 0;
|
|
382
|
+
if (trimmed.startsWith(CONTRACT_ATTEMPT_PREFIX)) return trimmed;
|
|
383
|
+
if (/^\d+$/.test(trimmed)) return `${CONTRACT_ATTEMPT_PREFIX}${trimmed}`;
|
|
384
|
+
return trimmed;
|
|
385
|
+
}
|
|
386
|
+
function parseAttemptRunId(attemptLike) {
|
|
387
|
+
const attemptId = normalizeAttemptId(attemptLike);
|
|
388
|
+
if (!attemptId) return null;
|
|
389
|
+
const matched = attemptId.match(/^run[-_:]?(\d+)$/i);
|
|
390
|
+
if (!matched) return null;
|
|
391
|
+
const value = Number(matched[1]);
|
|
392
|
+
return Number.isInteger(value) && value > 0 ? value : null;
|
|
393
|
+
}
|
|
394
|
+
function hashContractPayload(payload) {
|
|
395
|
+
const normalized = payload && typeof payload === "object" ? payload : { value: payload ?? null };
|
|
396
|
+
return (0, import_crypto.createHash)("sha256").update(JSON.stringify(normalized)).digest("hex");
|
|
397
|
+
}
|
|
398
|
+
function createContractEventId() {
|
|
399
|
+
return (0, import_crypto.randomUUID)();
|
|
400
|
+
}
|
|
401
|
+
function buildContractSignatureMessage({
|
|
402
|
+
eventId,
|
|
403
|
+
eventType,
|
|
404
|
+
taskId,
|
|
405
|
+
attemptId,
|
|
406
|
+
timestamp,
|
|
407
|
+
payload
|
|
408
|
+
}) {
|
|
409
|
+
return [
|
|
410
|
+
String(eventId || ""),
|
|
411
|
+
String(normalizeContractEvent(String(eventType || "")) || eventType || ""),
|
|
412
|
+
String(taskId || ""),
|
|
413
|
+
String(normalizeAttemptId(attemptId) || ""),
|
|
414
|
+
String(timestamp || ""),
|
|
415
|
+
hashContractPayload(payload ?? {})
|
|
416
|
+
].join(".");
|
|
417
|
+
}
|
|
418
|
+
function signContractEvent(input) {
|
|
419
|
+
const signatureMessage = buildContractSignatureMessage(input);
|
|
420
|
+
const digest = (0, import_crypto.createHmac)(CONTRACT_SIGNATURE_ALGORITHM, input.secret).update(signatureMessage).digest("hex");
|
|
421
|
+
return `${CONTRACT_SIGNATURE_ALGORITHM}=${digest}`;
|
|
422
|
+
}
|
|
423
|
+
function canonicalEventToLegacyAction(eventLike) {
|
|
424
|
+
const normalized = normalizeContractEvent(eventLike);
|
|
425
|
+
if (!normalized) return null;
|
|
426
|
+
return CONTRACT_LEGACY_ACTIONS[normalized] || null;
|
|
427
|
+
}
|
|
428
|
+
function buildContractIdempotencyKey({
|
|
429
|
+
taskId,
|
|
430
|
+
eventType,
|
|
431
|
+
eventId,
|
|
432
|
+
attemptId,
|
|
433
|
+
machineId,
|
|
434
|
+
timestamp,
|
|
435
|
+
payload
|
|
436
|
+
}) {
|
|
437
|
+
const canonicalEvent = normalizeContractEvent(eventType) || eventType;
|
|
438
|
+
if (eventId) {
|
|
439
|
+
const eventToken = (0, import_crypto.createHash)("sha256").update(String(eventId)).digest("hex");
|
|
440
|
+
return `${taskId}:${canonicalEvent}:event:${eventToken}`;
|
|
441
|
+
}
|
|
442
|
+
const hashInput = {
|
|
443
|
+
task_id: taskId,
|
|
444
|
+
event_type: canonicalEvent,
|
|
445
|
+
attempt_id: normalizeAttemptId(attemptId),
|
|
446
|
+
machine_id: machineId || null,
|
|
447
|
+
timestamp: timestamp || null,
|
|
448
|
+
payload_hash: hashContractPayload(payload ?? {})
|
|
449
|
+
};
|
|
450
|
+
const token = (0, import_crypto.createHash)("sha256").update(JSON.stringify(hashInput)).digest("hex");
|
|
451
|
+
return `${taskId}:${canonicalEvent}:hash:${token}`;
|
|
452
|
+
}
|
|
453
|
+
var ContractModule = class {
|
|
454
|
+
constructor(realtimexUrl, appName, appId, apiKey) {
|
|
455
|
+
this.cachedContract = null;
|
|
456
|
+
this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
|
|
457
|
+
this.appName = appName;
|
|
458
|
+
this.appId = appId;
|
|
459
|
+
this.apiKey = apiKey;
|
|
460
|
+
}
|
|
461
|
+
async requestPermission(permission) {
|
|
462
|
+
try {
|
|
463
|
+
const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
|
|
464
|
+
method: "POST",
|
|
465
|
+
headers: { "Content-Type": "application/json" },
|
|
466
|
+
body: JSON.stringify({
|
|
467
|
+
app_id: this.appId,
|
|
468
|
+
app_name: this.appName,
|
|
469
|
+
permission
|
|
470
|
+
})
|
|
471
|
+
});
|
|
472
|
+
const data = await response.json();
|
|
473
|
+
return data.granted === true;
|
|
474
|
+
} catch {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async request(path) {
|
|
479
|
+
const url = `${this.realtimexUrl}${path}`;
|
|
480
|
+
const headers = {
|
|
481
|
+
"Content-Type": "application/json"
|
|
482
|
+
};
|
|
483
|
+
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
484
|
+
if (this.appId) headers["x-app-id"] = this.appId;
|
|
485
|
+
const response = await fetch(url, {
|
|
486
|
+
method: "GET",
|
|
487
|
+
headers
|
|
488
|
+
});
|
|
489
|
+
const data = await response.json();
|
|
490
|
+
if (response.status === 403) {
|
|
491
|
+
const errorCode = data.error;
|
|
492
|
+
const permission = data.permission;
|
|
493
|
+
const message = data.message;
|
|
494
|
+
if (errorCode === "PERMISSION_REQUIRED" && permission) {
|
|
495
|
+
const granted = await this.requestPermission(permission);
|
|
496
|
+
if (granted) return this.request(path);
|
|
497
|
+
throw new PermissionDeniedError(permission, message);
|
|
498
|
+
}
|
|
499
|
+
if (errorCode === "PERMISSION_DENIED") {
|
|
500
|
+
throw new PermissionDeniedError(permission, message);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (!response.ok) {
|
|
504
|
+
throw new Error(data.error || `Request failed: ${response.status}`);
|
|
505
|
+
}
|
|
506
|
+
return data;
|
|
507
|
+
}
|
|
508
|
+
async getLocalAppV1(forceRefresh = false) {
|
|
509
|
+
if (!forceRefresh && this.cachedContract) return this.cachedContract;
|
|
510
|
+
const data = await this.request("/contracts/local-app/v1");
|
|
511
|
+
this.cachedContract = data.contract;
|
|
512
|
+
return data.contract;
|
|
513
|
+
}
|
|
514
|
+
clearCache() {
|
|
515
|
+
this.cachedContract = null;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
|
|
282
519
|
// src/modules/webhook.ts
|
|
283
520
|
var WebhookModule = class {
|
|
284
521
|
constructor(realtimexUrl, appName, appId, apiKey) {
|
|
@@ -355,7 +592,9 @@ var WebhookModule = class {
|
|
|
355
592
|
body: JSON.stringify({
|
|
356
593
|
app_name: this.appName,
|
|
357
594
|
app_id: this.appId,
|
|
358
|
-
event: "trigger
|
|
595
|
+
event: "task.trigger",
|
|
596
|
+
event_id: payload.event_id || createContractEventId(),
|
|
597
|
+
attempt_id: normalizeAttemptId(payload.attempt_id),
|
|
359
598
|
payload: {
|
|
360
599
|
raw_data: payload.raw_data,
|
|
361
600
|
auto_run: payload.auto_run ?? false,
|
|
@@ -373,7 +612,8 @@ var WebhookModule = class {
|
|
|
373
612
|
body: JSON.stringify({
|
|
374
613
|
app_name: this.appName,
|
|
375
614
|
app_id: this.appId,
|
|
376
|
-
event: "ping"
|
|
615
|
+
event: "system.ping",
|
|
616
|
+
event_id: createContractEventId()
|
|
377
617
|
})
|
|
378
618
|
});
|
|
379
619
|
}
|
|
@@ -386,49 +626,148 @@ var TaskModule = class {
|
|
|
386
626
|
this.appName = appName;
|
|
387
627
|
this.appId = appId;
|
|
388
628
|
this.apiKey = apiKey;
|
|
629
|
+
this.callbackSecret = process.env.RTX_CONTRACT_CALLBACK_SECRET;
|
|
630
|
+
this.signCallbacksByDefault = process.env.RTX_CONTRACT_SIGN_CALLBACKS === "true";
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Configure callback signing behavior.
|
|
634
|
+
*/
|
|
635
|
+
configureContract(config) {
|
|
636
|
+
if (typeof config.callbackSecret === "string") {
|
|
637
|
+
this.callbackSecret = config.callbackSecret;
|
|
638
|
+
}
|
|
639
|
+
if (typeof config.signCallbacksByDefault === "boolean") {
|
|
640
|
+
this.signCallbacksByDefault = config.signCallbacksByDefault;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Claim a task before processing.
|
|
645
|
+
*/
|
|
646
|
+
async claim(taskUuid, options = {}) {
|
|
647
|
+
return this._sendEvent("task.claimed", taskUuid, {}, options);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Alias for claim()
|
|
651
|
+
*/
|
|
652
|
+
async claimed(taskUuid, options = {}) {
|
|
653
|
+
return this.claim(taskUuid, options);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Mark task as processing.
|
|
657
|
+
* Backward compatible signature: start(taskUuid, machineId?)
|
|
658
|
+
*/
|
|
659
|
+
async start(taskUuid, machineIdOrOptions) {
|
|
660
|
+
return this._sendEvent("task.started", taskUuid, {}, this._normalizeOptions(machineIdOrOptions));
|
|
389
661
|
}
|
|
390
662
|
/**
|
|
391
|
-
*
|
|
663
|
+
* Report incremental task progress.
|
|
392
664
|
*/
|
|
393
|
-
async
|
|
394
|
-
return this._sendEvent("task
|
|
665
|
+
async progress(taskUuid, progressData = {}, options = {}) {
|
|
666
|
+
return this._sendEvent("task.progress", taskUuid, progressData, options);
|
|
395
667
|
}
|
|
396
668
|
/**
|
|
397
|
-
* Mark task as completed with result
|
|
669
|
+
* Mark task as completed with result.
|
|
670
|
+
* Backward compatible signature: complete(taskUuid, result?, machineId?)
|
|
398
671
|
*/
|
|
399
|
-
async complete(taskUuid, result,
|
|
400
|
-
return this._sendEvent("task
|
|
672
|
+
async complete(taskUuid, result = {}, machineIdOrOptions) {
|
|
673
|
+
return this._sendEvent("task.completed", taskUuid, { result }, this._normalizeOptions(machineIdOrOptions));
|
|
401
674
|
}
|
|
402
675
|
/**
|
|
403
|
-
* Mark task as failed with error
|
|
676
|
+
* Mark task as failed with error.
|
|
677
|
+
* Backward compatible signature: fail(taskUuid, error, machineId?)
|
|
404
678
|
*/
|
|
405
|
-
async fail(taskUuid, error,
|
|
406
|
-
return this._sendEvent("task
|
|
679
|
+
async fail(taskUuid, error, machineIdOrOptions) {
|
|
680
|
+
return this._sendEvent("task.failed", taskUuid, { error }, this._normalizeOptions(machineIdOrOptions));
|
|
407
681
|
}
|
|
408
|
-
|
|
682
|
+
/**
|
|
683
|
+
* Mark task as canceled.
|
|
684
|
+
*/
|
|
685
|
+
async cancel(taskUuid, reason, options = {}) {
|
|
686
|
+
const payload = reason ? { error: reason } : {};
|
|
687
|
+
return this._sendEvent("task.canceled", taskUuid, payload, options);
|
|
688
|
+
}
|
|
689
|
+
_normalizeOptions(machineIdOrOptions) {
|
|
690
|
+
if (!machineIdOrOptions) return {};
|
|
691
|
+
if (typeof machineIdOrOptions === "string") {
|
|
692
|
+
return { machineId: machineIdOrOptions };
|
|
693
|
+
}
|
|
694
|
+
return machineIdOrOptions;
|
|
695
|
+
}
|
|
696
|
+
async _sendEvent(event, taskUuid, eventData = {}, options = {}) {
|
|
697
|
+
if (!taskUuid || !taskUuid.trim()) {
|
|
698
|
+
throw new Error("taskUuid is required");
|
|
699
|
+
}
|
|
700
|
+
const attemptId = normalizeAttemptId(options.attemptId);
|
|
701
|
+
const timestamp = options.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
702
|
+
const eventId = options.eventId || createContractEventId();
|
|
703
|
+
const callbackUrl = options.callbackUrl;
|
|
704
|
+
const targetUrl = callbackUrl || `${this.realtimexUrl}/webhooks/realtimex`;
|
|
705
|
+
const sendingToMainWebhook = !callbackUrl;
|
|
706
|
+
const includeAppAuth = sendingToMainWebhook || targetUrl.startsWith(this.realtimexUrl);
|
|
707
|
+
const payloadData = eventData && typeof eventData === "object" ? eventData : {};
|
|
409
708
|
const headers = { "Content-Type": "application/json" };
|
|
410
|
-
|
|
411
|
-
|
|
709
|
+
headers[CONTRACT_EVENT_ID_HEADER] = eventId;
|
|
710
|
+
if (includeAppAuth) {
|
|
711
|
+
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
712
|
+
if (this.appId) headers["x-app-id"] = this.appId;
|
|
412
713
|
}
|
|
413
|
-
|
|
414
|
-
|
|
714
|
+
const callbackSecret = options.callbackSecret || this.callbackSecret;
|
|
715
|
+
const shouldSign = options.sign ?? this.signCallbacksByDefault;
|
|
716
|
+
if (shouldSign) {
|
|
717
|
+
if (!callbackSecret) {
|
|
718
|
+
throw new Error(
|
|
719
|
+
"Callback signing is enabled but no callbackSecret is configured. Use task.configureContract({ callbackSecret }) or pass options.callbackSecret."
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
headers[CONTRACT_SIGNATURE_HEADER] = signContractEvent({
|
|
723
|
+
secret: callbackSecret,
|
|
724
|
+
eventId,
|
|
725
|
+
eventType: event,
|
|
726
|
+
taskId: taskUuid,
|
|
727
|
+
attemptId,
|
|
728
|
+
timestamp,
|
|
729
|
+
payload: payloadData
|
|
730
|
+
});
|
|
415
731
|
}
|
|
416
|
-
const
|
|
732
|
+
const requestBody = sendingToMainWebhook ? {
|
|
733
|
+
app_name: this.appName,
|
|
734
|
+
app_id: this.appId,
|
|
735
|
+
event,
|
|
736
|
+
event_id: eventId,
|
|
737
|
+
attempt_id: attemptId,
|
|
738
|
+
payload: {
|
|
739
|
+
task_uuid: taskUuid,
|
|
740
|
+
machine_id: options.machineId,
|
|
741
|
+
timestamp,
|
|
742
|
+
attempt_id: attemptId,
|
|
743
|
+
...payloadData
|
|
744
|
+
}
|
|
745
|
+
} : {
|
|
746
|
+
event,
|
|
747
|
+
action: canonicalEventToLegacyAction(event),
|
|
748
|
+
event_id: eventId,
|
|
749
|
+
attempt_id: attemptId,
|
|
750
|
+
machine_id: options.machineId,
|
|
751
|
+
user_email: options.userEmail,
|
|
752
|
+
activity_id: options.activityId,
|
|
753
|
+
table_name: options.tableName,
|
|
754
|
+
timestamp,
|
|
755
|
+
data: payloadData
|
|
756
|
+
};
|
|
757
|
+
const response = await fetch(targetUrl, {
|
|
417
758
|
method: "POST",
|
|
418
759
|
headers,
|
|
419
|
-
body: JSON.stringify(
|
|
420
|
-
app_name: this.appName,
|
|
421
|
-
app_id: this.appId,
|
|
422
|
-
event,
|
|
423
|
-
payload: {
|
|
424
|
-
task_uuid: taskUuid,
|
|
425
|
-
...extra
|
|
426
|
-
}
|
|
427
|
-
})
|
|
760
|
+
body: JSON.stringify(requestBody)
|
|
428
761
|
});
|
|
429
|
-
const
|
|
430
|
-
if (!response.ok) throw new Error(
|
|
431
|
-
return
|
|
762
|
+
const responseData = await response.json();
|
|
763
|
+
if (!response.ok) throw new Error(responseData.error || `Failed to ${event}`);
|
|
764
|
+
return {
|
|
765
|
+
...responseData,
|
|
766
|
+
task_uuid: responseData.task_uuid || responseData.task_id || taskUuid,
|
|
767
|
+
event_id: responseData.event_id || eventId,
|
|
768
|
+
attempt_id: responseData.attempt_id || attemptId,
|
|
769
|
+
event_type: responseData.event_type || event
|
|
770
|
+
};
|
|
432
771
|
}
|
|
433
772
|
};
|
|
434
773
|
|
|
@@ -1464,6 +1803,1485 @@ var HttpClient = class {
|
|
|
1464
1803
|
}
|
|
1465
1804
|
};
|
|
1466
1805
|
|
|
1806
|
+
// src/core/errors/ContractErrors.ts
|
|
1807
|
+
var ContractError = class extends Error {
|
|
1808
|
+
constructor(code, message, details) {
|
|
1809
|
+
super(message);
|
|
1810
|
+
this.name = this.constructor.name;
|
|
1811
|
+
this.code = code;
|
|
1812
|
+
this.details = details;
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1815
|
+
var ContractValidationError = class extends ContractError {
|
|
1816
|
+
constructor(message, details) {
|
|
1817
|
+
super("contract_validation_error", message, details);
|
|
1818
|
+
}
|
|
1819
|
+
};
|
|
1820
|
+
var ToolValidationError = class extends ContractError {
|
|
1821
|
+
constructor(message, details) {
|
|
1822
|
+
super("tool_validation_error", message, details);
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
var ToolNotFoundError = class extends ContractError {
|
|
1826
|
+
constructor(toolName, details) {
|
|
1827
|
+
super("tool_not_found", `Tool not found: ${toolName}`, details);
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1830
|
+
var ScopeDeniedError = class extends ContractError {
|
|
1831
|
+
constructor(permission, details) {
|
|
1832
|
+
super("scope_denied", `Missing required permission: ${permission}`, details);
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
var RuntimeTransportError = class extends ContractError {
|
|
1836
|
+
constructor(message, statusCode, details) {
|
|
1837
|
+
super("runtime_transport_error", message, details);
|
|
1838
|
+
this.statusCode = statusCode;
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
|
|
1842
|
+
// src/core/auth/ScopeGuard.ts
|
|
1843
|
+
var ScopeGuard = class {
|
|
1844
|
+
constructor(scopes = []) {
|
|
1845
|
+
this.scopes = new Set(scopes.filter((scope) => scope.trim().length > 0));
|
|
1846
|
+
}
|
|
1847
|
+
can(permission) {
|
|
1848
|
+
if (!permission) return true;
|
|
1849
|
+
if (this.scopes.size === 0) return true;
|
|
1850
|
+
return this.scopes.has(permission);
|
|
1851
|
+
}
|
|
1852
|
+
assert(permission) {
|
|
1853
|
+
if (!permission) return;
|
|
1854
|
+
if (!this.can(permission)) {
|
|
1855
|
+
throw new ScopeDeniedError(permission);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
// src/core/contract/ContractCache.ts
|
|
1861
|
+
var ContractCache = class {
|
|
1862
|
+
constructor(ttlMs = 3e4) {
|
|
1863
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
1864
|
+
this.ttlMs = ttlMs > 0 ? ttlMs : null;
|
|
1865
|
+
}
|
|
1866
|
+
get(key) {
|
|
1867
|
+
const entry = this.entries.get(key);
|
|
1868
|
+
if (!entry) return null;
|
|
1869
|
+
if (entry.expiresAt !== null && entry.expiresAt <= Date.now()) {
|
|
1870
|
+
this.entries.delete(key);
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
return entry.value;
|
|
1874
|
+
}
|
|
1875
|
+
set(key, value) {
|
|
1876
|
+
const expiresAt = this.ttlMs === null ? null : Date.now() + this.ttlMs;
|
|
1877
|
+
this.entries.set(key, { value, expiresAt });
|
|
1878
|
+
}
|
|
1879
|
+
clear(key) {
|
|
1880
|
+
if (key) {
|
|
1881
|
+
this.entries.delete(key);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
this.entries.clear();
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1888
|
+
// src/core/contract/ContractValidator.ts
|
|
1889
|
+
var LOCAL_APP_CONTRACT_VERSION2 = "local-app-contract/v1";
|
|
1890
|
+
var DEFAULT_SUPPORTED_EVENTS = [
|
|
1891
|
+
"task.trigger",
|
|
1892
|
+
"system.ping",
|
|
1893
|
+
"task.claimed",
|
|
1894
|
+
"task.started",
|
|
1895
|
+
"task.progress",
|
|
1896
|
+
"task.completed",
|
|
1897
|
+
"task.failed",
|
|
1898
|
+
"task.canceled"
|
|
1899
|
+
];
|
|
1900
|
+
function isRecord(value) {
|
|
1901
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1902
|
+
}
|
|
1903
|
+
function asStringArray(value) {
|
|
1904
|
+
if (!Array.isArray(value)) return [];
|
|
1905
|
+
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
1906
|
+
}
|
|
1907
|
+
function asStringRecord(value) {
|
|
1908
|
+
if (!isRecord(value)) return void 0;
|
|
1909
|
+
const entries = Object.entries(value).filter(
|
|
1910
|
+
([key, item]) => key.trim().length > 0 && typeof item === "string"
|
|
1911
|
+
);
|
|
1912
|
+
if (entries.length === 0) return void 0;
|
|
1913
|
+
return entries.reduce((accumulator, [key, item]) => {
|
|
1914
|
+
accumulator[key] = item;
|
|
1915
|
+
return accumulator;
|
|
1916
|
+
}, {});
|
|
1917
|
+
}
|
|
1918
|
+
function normalizeStrictness(value) {
|
|
1919
|
+
return value === "strict" ? "strict" : "compatible";
|
|
1920
|
+
}
|
|
1921
|
+
function normalizeCallbackRules(value) {
|
|
1922
|
+
if (!isRecord(value)) return void 0;
|
|
1923
|
+
const callback = {};
|
|
1924
|
+
if (typeof value.event_id_header === "string") callback.event_id_header = value.event_id_header;
|
|
1925
|
+
if (typeof value.signature_header === "string") callback.signature_header = value.signature_header;
|
|
1926
|
+
if (typeof value.signature_algorithm === "string") callback.signature_algorithm = value.signature_algorithm;
|
|
1927
|
+
if (typeof value.signature_message === "string") callback.signature_message = value.signature_message;
|
|
1928
|
+
if (typeof value.attempt_id_format === "string") callback.attempt_id_format = value.attempt_id_format;
|
|
1929
|
+
if (typeof value.idempotency === "string") callback.idempotency = value.idempotency;
|
|
1930
|
+
return Object.keys(callback).length > 0 ? callback : void 0;
|
|
1931
|
+
}
|
|
1932
|
+
function normalizeTrigger(value) {
|
|
1933
|
+
if (!isRecord(value)) {
|
|
1934
|
+
return { event: "task.trigger" };
|
|
1935
|
+
}
|
|
1936
|
+
const event = value.event === "task.trigger" ? "task.trigger" : "task.trigger";
|
|
1937
|
+
const route = typeof value.route === "string" && value.route.trim().length > 0 ? value.route : void 0;
|
|
1938
|
+
const payloadTemplate = isRecord(value.payload_template) ? value.payload_template : void 0;
|
|
1939
|
+
return {
|
|
1940
|
+
event,
|
|
1941
|
+
route,
|
|
1942
|
+
payload_template: payloadTemplate
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
function normalizeCapability(value) {
|
|
1946
|
+
if (!isRecord(value)) {
|
|
1947
|
+
throw new ContractValidationError("Invalid capability: expected object");
|
|
1948
|
+
}
|
|
1949
|
+
const capabilityId = typeof value.capability_id === "string" ? value.capability_id.trim() : "";
|
|
1950
|
+
const name = typeof value.name === "string" ? value.name.trim() : "";
|
|
1951
|
+
const description = typeof value.description === "string" ? value.description.trim() : "";
|
|
1952
|
+
const permission = typeof value.permission === "string" ? value.permission.trim() : "";
|
|
1953
|
+
if (!capabilityId || !name || !description || !permission) {
|
|
1954
|
+
throw new ContractValidationError("Invalid capability: missing required fields", { capability: value });
|
|
1955
|
+
}
|
|
1956
|
+
if (!isRecord(value.input_schema)) {
|
|
1957
|
+
throw new ContractValidationError("Invalid capability input_schema: expected JSON schema object", {
|
|
1958
|
+
capability_id: capabilityId
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
return {
|
|
1962
|
+
capability_id: capabilityId,
|
|
1963
|
+
name,
|
|
1964
|
+
description,
|
|
1965
|
+
input_schema: value.input_schema,
|
|
1966
|
+
output_schema: isRecord(value.output_schema) ? value.output_schema : void 0,
|
|
1967
|
+
permission,
|
|
1968
|
+
trigger: normalizeTrigger(value.trigger)
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
function normalizeCapabilities(value) {
|
|
1972
|
+
if (value === void 0 || value === null) return void 0;
|
|
1973
|
+
if (!Array.isArray(value)) {
|
|
1974
|
+
throw new ContractValidationError("Invalid capabilities: expected array");
|
|
1975
|
+
}
|
|
1976
|
+
return value.map(normalizeCapability);
|
|
1977
|
+
}
|
|
1978
|
+
function extractContractRecord(payload) {
|
|
1979
|
+
if (!isRecord(payload)) {
|
|
1980
|
+
throw new ContractValidationError("Invalid contract response: expected object payload");
|
|
1981
|
+
}
|
|
1982
|
+
if (isRecord(payload.contract)) {
|
|
1983
|
+
return payload.contract;
|
|
1984
|
+
}
|
|
1985
|
+
return payload;
|
|
1986
|
+
}
|
|
1987
|
+
function resolveContractVersion(contract) {
|
|
1988
|
+
if (typeof contract.contract_version === "string") return contract.contract_version;
|
|
1989
|
+
if (typeof contract.version === "string") return contract.version;
|
|
1990
|
+
if (typeof contract.id === "string") return contract.id;
|
|
1991
|
+
return "";
|
|
1992
|
+
}
|
|
1993
|
+
function normalizeLocalAppContractV1(payload) {
|
|
1994
|
+
const contract = extractContractRecord(payload);
|
|
1995
|
+
const version = resolveContractVersion(contract);
|
|
1996
|
+
if (!version) {
|
|
1997
|
+
throw new ContractValidationError("Missing contract version in discovery response");
|
|
1998
|
+
}
|
|
1999
|
+
if (version !== LOCAL_APP_CONTRACT_VERSION2) {
|
|
2000
|
+
throw new ContractValidationError("Unsupported contract version", {
|
|
2001
|
+
expected: LOCAL_APP_CONTRACT_VERSION2,
|
|
2002
|
+
received: version
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
const supportedContractEvents = asStringArray(contract.supported_contract_events).length > 0 ? asStringArray(contract.supported_contract_events) : asStringArray(contract.supported_events).length > 0 ? asStringArray(contract.supported_events) : DEFAULT_SUPPORTED_EVENTS;
|
|
2006
|
+
return {
|
|
2007
|
+
contract_version: LOCAL_APP_CONTRACT_VERSION2,
|
|
2008
|
+
strictness: normalizeStrictness(contract.strictness),
|
|
2009
|
+
supported_contract_events: supportedContractEvents,
|
|
2010
|
+
supported_legacy_events: asStringArray(contract.supported_legacy_events),
|
|
2011
|
+
aliases: asStringRecord(contract.aliases),
|
|
2012
|
+
status_map: asStringRecord(contract.status_map),
|
|
2013
|
+
legacy_action_map: asStringRecord(contract.legacy_action_map),
|
|
2014
|
+
callback: normalizeCallbackRules(contract.callback),
|
|
2015
|
+
capabilities: normalizeCapabilities(contract.capabilities)
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
// src/core/contract/ContractClient.ts
|
|
2020
|
+
var ContractClient = class {
|
|
2021
|
+
constructor(httpClient, options = {}) {
|
|
2022
|
+
this.httpClient = httpClient;
|
|
2023
|
+
this.cache = options.cache || new ContractCache();
|
|
2024
|
+
this.cacheKey = options.cacheKey || "local-app-contract/v1";
|
|
2025
|
+
}
|
|
2026
|
+
async getLocalAppV1(forceRefresh = false) {
|
|
2027
|
+
if (!forceRefresh) {
|
|
2028
|
+
const cached = this.cache.get(this.cacheKey);
|
|
2029
|
+
if (cached) return cached;
|
|
2030
|
+
}
|
|
2031
|
+
const response = await this.httpClient.get("/contracts/local-app/v1");
|
|
2032
|
+
const normalized = normalizeLocalAppContractV1(response);
|
|
2033
|
+
this.cache.set(this.cacheKey, normalized);
|
|
2034
|
+
return normalized;
|
|
2035
|
+
}
|
|
2036
|
+
clearCache() {
|
|
2037
|
+
this.cache.clear(this.cacheKey);
|
|
2038
|
+
}
|
|
2039
|
+
};
|
|
2040
|
+
|
|
2041
|
+
// src/core/transport/HttpClient.ts
|
|
2042
|
+
function toRecordHeaders(input = {}) {
|
|
2043
|
+
if (input instanceof Headers) {
|
|
2044
|
+
const normalized = {};
|
|
2045
|
+
input.forEach((value, key) => {
|
|
2046
|
+
normalized[key] = value;
|
|
2047
|
+
});
|
|
2048
|
+
return normalized;
|
|
2049
|
+
}
|
|
2050
|
+
if (Array.isArray(input)) {
|
|
2051
|
+
return Object.fromEntries(input);
|
|
2052
|
+
}
|
|
2053
|
+
return { ...input };
|
|
2054
|
+
}
|
|
2055
|
+
var ContractHttpClient = class {
|
|
2056
|
+
constructor(config) {
|
|
2057
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
2058
|
+
this.appId = config.appId;
|
|
2059
|
+
this.appName = config.appName;
|
|
2060
|
+
this.apiKey = config.apiKey;
|
|
2061
|
+
this.fetchImpl = config.fetchImpl || fetch;
|
|
2062
|
+
}
|
|
2063
|
+
async get(path, headers = {}) {
|
|
2064
|
+
return this.request(path, {
|
|
2065
|
+
method: "GET",
|
|
2066
|
+
headers
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
async post(path, body, headers = {}) {
|
|
2070
|
+
return this.request(path, {
|
|
2071
|
+
method: "POST",
|
|
2072
|
+
headers,
|
|
2073
|
+
body: JSON.stringify(body)
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
async request(path, options = {}) {
|
|
2077
|
+
const url = `${this.baseUrl}${path}`;
|
|
2078
|
+
const headers = this.buildHeaders(options.headers);
|
|
2079
|
+
let response;
|
|
2080
|
+
try {
|
|
2081
|
+
response = await this.fetchImpl(url, {
|
|
2082
|
+
...options,
|
|
2083
|
+
headers
|
|
2084
|
+
});
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
throw new RuntimeTransportError("Failed to reach RealtimeX server", void 0, {
|
|
2087
|
+
cause: error,
|
|
2088
|
+
url
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
const payload = await this.parseResponse(response);
|
|
2092
|
+
if (!response.ok) {
|
|
2093
|
+
const errorMessage = payload && typeof payload === "object" && "error" in payload && typeof payload.error === "string" ? payload.error : `Request failed with status ${response.status}`;
|
|
2094
|
+
throw new RuntimeTransportError(errorMessage, response.status, payload);
|
|
2095
|
+
}
|
|
2096
|
+
return payload;
|
|
2097
|
+
}
|
|
2098
|
+
buildHeaders(extraHeaders = {}) {
|
|
2099
|
+
const headers = {
|
|
2100
|
+
"Content-Type": "application/json",
|
|
2101
|
+
...toRecordHeaders(extraHeaders)
|
|
2102
|
+
};
|
|
2103
|
+
if (this.apiKey) {
|
|
2104
|
+
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
2105
|
+
}
|
|
2106
|
+
if (this.appId) {
|
|
2107
|
+
headers["x-app-id"] = this.appId;
|
|
2108
|
+
}
|
|
2109
|
+
if (this.appName) {
|
|
2110
|
+
headers["x-app-name"] = this.appName;
|
|
2111
|
+
}
|
|
2112
|
+
return headers;
|
|
2113
|
+
}
|
|
2114
|
+
async parseResponse(response) {
|
|
2115
|
+
const contentType = response.headers.get("content-type") || "";
|
|
2116
|
+
if (contentType.includes("application/json")) {
|
|
2117
|
+
try {
|
|
2118
|
+
return await response.json();
|
|
2119
|
+
} catch {
|
|
2120
|
+
return {};
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
const text = await response.text();
|
|
2124
|
+
if (!text) return {};
|
|
2125
|
+
try {
|
|
2126
|
+
return JSON.parse(text);
|
|
2127
|
+
} catch {
|
|
2128
|
+
return { text };
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
};
|
|
2132
|
+
|
|
2133
|
+
// src/core/tooling/SchemaNormalizer.ts
|
|
2134
|
+
function isRecord2(value) {
|
|
2135
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2136
|
+
}
|
|
2137
|
+
function deepClone(value) {
|
|
2138
|
+
return JSON.parse(JSON.stringify(value));
|
|
2139
|
+
}
|
|
2140
|
+
function normalizeSchema(schema) {
|
|
2141
|
+
if (!schema || !isRecord2(schema)) {
|
|
2142
|
+
return {
|
|
2143
|
+
type: "object",
|
|
2144
|
+
properties: {},
|
|
2145
|
+
additionalProperties: true
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
const normalized = deepClone(schema);
|
|
2149
|
+
if (typeof normalized.type !== "string" && !Array.isArray(normalized.type)) {
|
|
2150
|
+
normalized.type = "object";
|
|
2151
|
+
}
|
|
2152
|
+
if (normalized.type === "object" && !isRecord2(normalized.properties)) {
|
|
2153
|
+
normalized.properties = {};
|
|
2154
|
+
}
|
|
2155
|
+
if (Array.isArray(normalized.required)) {
|
|
2156
|
+
normalized.required = normalized.required.filter(
|
|
2157
|
+
(field) => typeof field === "string" && field.trim().length > 0
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
return normalized;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// src/core/tooling/ToolNamePolicy.ts
|
|
2164
|
+
function normalizeToken(value) {
|
|
2165
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2166
|
+
}
|
|
2167
|
+
function toStableToolName(capabilityId, namespace) {
|
|
2168
|
+
const normalizedCapability = normalizeToken(capabilityId.replace(/\./g, "_"));
|
|
2169
|
+
const normalizedNamespace = namespace ? normalizeToken(namespace) : "";
|
|
2170
|
+
if (!normalizedCapability) {
|
|
2171
|
+
return normalizedNamespace ? `${normalizedNamespace}_tool` : "tool";
|
|
2172
|
+
}
|
|
2173
|
+
const combined = normalizedNamespace && !normalizedCapability.startsWith(`${normalizedNamespace}_`) ? `${normalizedNamespace}_${normalizedCapability}` : normalizedCapability;
|
|
2174
|
+
return /^[a-z]/.test(combined) ? combined : `tool_${combined}`;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// src/core/tooling/ToolProjector.ts
|
|
2178
|
+
var ToolProjector = class {
|
|
2179
|
+
project(input) {
|
|
2180
|
+
const capabilities = input.contract.capabilities || [];
|
|
2181
|
+
const names = /* @__PURE__ */ new Set();
|
|
2182
|
+
return capabilities.map((capability) => {
|
|
2183
|
+
const baseName = toStableToolName(capability.capability_id, input.namespace || input.appId);
|
|
2184
|
+
const uniqueName = this.ensureUniqueToolName(baseName, names);
|
|
2185
|
+
return {
|
|
2186
|
+
tool_name: uniqueName,
|
|
2187
|
+
title: capability.name,
|
|
2188
|
+
description: capability.description,
|
|
2189
|
+
input_schema: normalizeSchema(capability.input_schema),
|
|
2190
|
+
output_schema: capability.output_schema,
|
|
2191
|
+
permission: capability.permission,
|
|
2192
|
+
capability_id: capability.capability_id,
|
|
2193
|
+
trigger: capability.trigger
|
|
2194
|
+
};
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
ensureUniqueToolName(baseName, names) {
|
|
2198
|
+
if (!names.has(baseName)) {
|
|
2199
|
+
names.add(baseName);
|
|
2200
|
+
return baseName;
|
|
2201
|
+
}
|
|
2202
|
+
let index = 2;
|
|
2203
|
+
while (names.has(`${baseName}_${index}`)) {
|
|
2204
|
+
index += 1;
|
|
2205
|
+
}
|
|
2206
|
+
const uniqueName = `${baseName}_${index}`;
|
|
2207
|
+
names.add(uniqueName);
|
|
2208
|
+
return uniqueName;
|
|
2209
|
+
}
|
|
2210
|
+
};
|
|
2211
|
+
|
|
2212
|
+
// src/core/runtime/ExecutionStore.ts
|
|
2213
|
+
var ExecutionStore = class {
|
|
2214
|
+
constructor() {
|
|
2215
|
+
this.registries = /* @__PURE__ */ new Map();
|
|
2216
|
+
}
|
|
2217
|
+
registerTools(registryKey, tools) {
|
|
2218
|
+
const registry = /* @__PURE__ */ new Map();
|
|
2219
|
+
for (const tool of tools) {
|
|
2220
|
+
registry.set(tool.tool_name, tool);
|
|
2221
|
+
}
|
|
2222
|
+
this.registries.set(registryKey, registry);
|
|
2223
|
+
}
|
|
2224
|
+
hasRegistry(registryKey) {
|
|
2225
|
+
return this.registries.has(registryKey);
|
|
2226
|
+
}
|
|
2227
|
+
getTool(registryKey, toolName) {
|
|
2228
|
+
return this.registries.get(registryKey)?.get(toolName);
|
|
2229
|
+
}
|
|
2230
|
+
clear(registryKey) {
|
|
2231
|
+
if (registryKey) {
|
|
2232
|
+
this.registries.delete(registryKey);
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
this.registries.clear();
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
// src/core/runtime/LifecycleReporter.ts
|
|
2240
|
+
var LifecycleReporter = class {
|
|
2241
|
+
constructor() {
|
|
2242
|
+
this.handlers = /* @__PURE__ */ new Set();
|
|
2243
|
+
}
|
|
2244
|
+
onEvent(handler) {
|
|
2245
|
+
this.handlers.add(handler);
|
|
2246
|
+
return () => {
|
|
2247
|
+
this.handlers.delete(handler);
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
emit(event) {
|
|
2251
|
+
for (const handler of this.handlers) {
|
|
2252
|
+
try {
|
|
2253
|
+
handler(event);
|
|
2254
|
+
} catch {
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
|
|
2260
|
+
// src/core/runtime/RetryPolicy.ts
|
|
2261
|
+
function defaultShouldRetry(error) {
|
|
2262
|
+
if (error && typeof error === "object") {
|
|
2263
|
+
const status = error.statusCode;
|
|
2264
|
+
if (typeof status === "number") {
|
|
2265
|
+
return status >= 500 || status === 429;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
return true;
|
|
2269
|
+
}
|
|
2270
|
+
var RetryPolicy = class {
|
|
2271
|
+
constructor(options = {}) {
|
|
2272
|
+
this.maxAttempts = Math.max(1, options.maxAttempts ?? 3);
|
|
2273
|
+
this.baseDelayMs = Math.max(0, options.baseDelayMs ?? 150);
|
|
2274
|
+
this.maxDelayMs = Math.max(this.baseDelayMs, options.maxDelayMs ?? 2e3);
|
|
2275
|
+
this.shouldRetry = options.shouldRetry || ((error) => defaultShouldRetry(error));
|
|
2276
|
+
}
|
|
2277
|
+
async execute(operation) {
|
|
2278
|
+
let lastError = null;
|
|
2279
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt += 1) {
|
|
2280
|
+
try {
|
|
2281
|
+
return await operation(attempt);
|
|
2282
|
+
} catch (error) {
|
|
2283
|
+
lastError = error;
|
|
2284
|
+
const canRetry = attempt < this.maxAttempts && this.shouldRetry(error, attempt);
|
|
2285
|
+
if (!canRetry) throw error;
|
|
2286
|
+
const delayMs = Math.min(this.maxDelayMs, this.baseDelayMs * 2 ** (attempt - 1));
|
|
2287
|
+
if (delayMs > 0) {
|
|
2288
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
throw lastError instanceof Error ? lastError : new Error("Retry operation failed");
|
|
2293
|
+
}
|
|
2294
|
+
};
|
|
2295
|
+
|
|
2296
|
+
// src/core/runtime/ContractRuntime.ts
|
|
2297
|
+
var LIFECYCLE_EVENTS = /* @__PURE__ */ new Set([
|
|
2298
|
+
"task.claimed",
|
|
2299
|
+
"task.started",
|
|
2300
|
+
"task.progress",
|
|
2301
|
+
"task.completed",
|
|
2302
|
+
"task.failed",
|
|
2303
|
+
"task.canceled"
|
|
2304
|
+
]);
|
|
2305
|
+
var DEFAULT_PROVIDER = "gemini";
|
|
2306
|
+
function isRecord3(value) {
|
|
2307
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2308
|
+
}
|
|
2309
|
+
function toRecord(value) {
|
|
2310
|
+
return isRecord3(value) ? value : {};
|
|
2311
|
+
}
|
|
2312
|
+
function resolveToolCallId(input) {
|
|
2313
|
+
if (typeof input.tool_call_id === "string" && input.tool_call_id.trim().length > 0) {
|
|
2314
|
+
return input.tool_call_id;
|
|
2315
|
+
}
|
|
2316
|
+
const payload = isRecord3(input.payload) ? input.payload : void 0;
|
|
2317
|
+
if (payload && typeof payload.tool_call_id === "string" && payload.tool_call_id.trim().length > 0) {
|
|
2318
|
+
return payload.tool_call_id;
|
|
2319
|
+
}
|
|
2320
|
+
const data = isRecord3(input.data) ? input.data : void 0;
|
|
2321
|
+
if (data && typeof data.tool_call_id === "string" && data.tool_call_id.trim().length > 0) {
|
|
2322
|
+
return data.tool_call_id;
|
|
2323
|
+
}
|
|
2324
|
+
return void 0;
|
|
2325
|
+
}
|
|
2326
|
+
var ContractRuntime = class {
|
|
2327
|
+
constructor(options) {
|
|
2328
|
+
if (!options.baseUrl) {
|
|
2329
|
+
throw new ContractValidationError("ContractRuntime requires baseUrl");
|
|
2330
|
+
}
|
|
2331
|
+
this.appId = options.appId;
|
|
2332
|
+
this.appName = options.appName;
|
|
2333
|
+
this.defaultNamespace = options.namespace;
|
|
2334
|
+
this.httpClient = new ContractHttpClient({
|
|
2335
|
+
baseUrl: options.baseUrl,
|
|
2336
|
+
appId: options.appId,
|
|
2337
|
+
appName: options.appName,
|
|
2338
|
+
apiKey: options.apiKey,
|
|
2339
|
+
fetchImpl: options.fetchImpl
|
|
2340
|
+
});
|
|
2341
|
+
this.contractClient = new ContractClient(this.httpClient, {
|
|
2342
|
+
cache: new ContractCache(options.cacheTtlMs ?? 3e4)
|
|
2343
|
+
});
|
|
2344
|
+
this.toolProjector = new ToolProjector();
|
|
2345
|
+
this.executionStore = new ExecutionStore();
|
|
2346
|
+
this.lifecycleReporter = new LifecycleReporter();
|
|
2347
|
+
this.scopeGuard = new ScopeGuard(options.permissions || []);
|
|
2348
|
+
this.retryPolicy = new RetryPolicy(options.retry);
|
|
2349
|
+
}
|
|
2350
|
+
async getContract(appId) {
|
|
2351
|
+
this.assertAppContext(appId);
|
|
2352
|
+
return this.contractClient.getLocalAppV1(false);
|
|
2353
|
+
}
|
|
2354
|
+
async getTools(input) {
|
|
2355
|
+
this.assertAppContext(input.appId);
|
|
2356
|
+
const contract = await this.contractClient.getLocalAppV1(false);
|
|
2357
|
+
const tools = this.toolProjector.project({
|
|
2358
|
+
contract,
|
|
2359
|
+
provider: input.provider,
|
|
2360
|
+
appId: input.appId,
|
|
2361
|
+
namespace: input.namespace || this.defaultNamespace || input.appId
|
|
2362
|
+
});
|
|
2363
|
+
this.executionStore.registerTools(
|
|
2364
|
+
this.buildRegistryKey(input.appId, input.provider, input.namespace),
|
|
2365
|
+
tools
|
|
2366
|
+
);
|
|
2367
|
+
return tools;
|
|
2368
|
+
}
|
|
2369
|
+
async executeToolCall(call, context) {
|
|
2370
|
+
try {
|
|
2371
|
+
this.assertAppContext(context.appId);
|
|
2372
|
+
const provider = context.provider || DEFAULT_PROVIDER;
|
|
2373
|
+
const namespace = context.namespace || this.defaultNamespace || context.appId;
|
|
2374
|
+
const registryKey = this.buildRegistryKey(context.appId, provider, namespace);
|
|
2375
|
+
if (!this.executionStore.hasRegistry(registryKey)) {
|
|
2376
|
+
await this.getTools({
|
|
2377
|
+
appId: context.appId,
|
|
2378
|
+
provider,
|
|
2379
|
+
namespace
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
const tool = this.executionStore.getTool(registryKey, call.tool_name);
|
|
2383
|
+
if (!tool) {
|
|
2384
|
+
throw new ToolNotFoundError(call.tool_name, {
|
|
2385
|
+
registryKey
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
this.scopeGuard.assert(tool.permission);
|
|
2389
|
+
const validationErrors = this.validateToolArgs(tool, call.args);
|
|
2390
|
+
if (validationErrors.length > 0) {
|
|
2391
|
+
throw new ToolValidationError("Tool input validation failed", {
|
|
2392
|
+
tool_name: call.tool_name,
|
|
2393
|
+
errors: validationErrors
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
const eventId = context.idempotencyKey || call.tool_call_id;
|
|
2397
|
+
const requestBody = this.buildTriggerRequestBody(call, context, tool, eventId);
|
|
2398
|
+
const response = await this.retryPolicy.execute(
|
|
2399
|
+
() => this.httpClient.post(tool.trigger.route || "/webhooks/realtimex", requestBody)
|
|
2400
|
+
);
|
|
2401
|
+
if (response.success === false) {
|
|
2402
|
+
throw new RuntimeTransportError(response.error || "Failed to trigger task", void 0, response);
|
|
2403
|
+
}
|
|
2404
|
+
const taskId = this.resolveTaskId(response);
|
|
2405
|
+
const attemptId = normalizeAttemptId(response.attempt_id);
|
|
2406
|
+
this.tryEmitLifecycleEvent({
|
|
2407
|
+
tool_call_id: call.tool_call_id,
|
|
2408
|
+
task_id: taskId,
|
|
2409
|
+
attempt_id: attemptId,
|
|
2410
|
+
event_type: response.event_type,
|
|
2411
|
+
event_id: response.event_id,
|
|
2412
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2413
|
+
payload: toRecord(response)
|
|
2414
|
+
});
|
|
2415
|
+
if (taskId) {
|
|
2416
|
+
return {
|
|
2417
|
+
status: "queued",
|
|
2418
|
+
tool_call_id: call.tool_call_id,
|
|
2419
|
+
task_id: taskId,
|
|
2420
|
+
attempt_id: attemptId,
|
|
2421
|
+
output: {
|
|
2422
|
+
message: response.message || "Task accepted"
|
|
2423
|
+
}
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
return {
|
|
2427
|
+
status: "completed",
|
|
2428
|
+
tool_call_id: call.tool_call_id,
|
|
2429
|
+
output: toRecord(response)
|
|
2430
|
+
};
|
|
2431
|
+
} catch (error) {
|
|
2432
|
+
return this.toFailedResult(call.tool_call_id, error);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
onExecutionEvent(handler) {
|
|
2436
|
+
return this.lifecycleReporter.onEvent(handler);
|
|
2437
|
+
}
|
|
2438
|
+
ingestExecutionEvent(input) {
|
|
2439
|
+
const eventTypeRaw = typeof input.event_type === "string" && input.event_type || typeof input.event === "string" && input.event || "";
|
|
2440
|
+
const normalized = normalizeContractEvent(eventTypeRaw);
|
|
2441
|
+
if (!normalized || !LIFECYCLE_EVENTS.has(normalized)) {
|
|
2442
|
+
return null;
|
|
2443
|
+
}
|
|
2444
|
+
const taskId = typeof input.task_id === "string" && input.task_id || typeof input.task_uuid === "string" && input.task_uuid || "";
|
|
2445
|
+
if (!taskId) return null;
|
|
2446
|
+
const event = {
|
|
2447
|
+
tool_call_id: resolveToolCallId(input) || taskId,
|
|
2448
|
+
task_id: taskId,
|
|
2449
|
+
attempt_id: normalizeAttemptId(input.attempt_id),
|
|
2450
|
+
event_type: normalized,
|
|
2451
|
+
event_id: typeof input.event_id === "string" && input.event_id || `${taskId}:${normalized}:${Date.now()}`,
|
|
2452
|
+
timestamp: typeof input.timestamp === "string" && input.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2453
|
+
payload: toRecord(input.payload || input.data)
|
|
2454
|
+
};
|
|
2455
|
+
this.lifecycleReporter.emit(event);
|
|
2456
|
+
return event;
|
|
2457
|
+
}
|
|
2458
|
+
clearCache() {
|
|
2459
|
+
this.contractClient.clearCache();
|
|
2460
|
+
this.executionStore.clear();
|
|
2461
|
+
}
|
|
2462
|
+
assertAppContext(appId) {
|
|
2463
|
+
if (!appId || appId.trim().length === 0) {
|
|
2464
|
+
throw new ContractValidationError("appId is required");
|
|
2465
|
+
}
|
|
2466
|
+
if (this.appId && this.appId !== appId) {
|
|
2467
|
+
throw new ContractValidationError("Runtime appId mismatch", {
|
|
2468
|
+
runtime_app_id: this.appId,
|
|
2469
|
+
requested_app_id: appId
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
buildRegistryKey(appId, provider, namespace) {
|
|
2474
|
+
const resolvedNamespace = namespace || this.defaultNamespace || appId;
|
|
2475
|
+
return `${appId}:${provider}:${resolvedNamespace}`;
|
|
2476
|
+
}
|
|
2477
|
+
resolveTaskId(response) {
|
|
2478
|
+
if (typeof response.task_uuid === "string" && response.task_uuid.trim().length > 0) {
|
|
2479
|
+
return response.task_uuid;
|
|
2480
|
+
}
|
|
2481
|
+
if (typeof response.task_id === "string" && response.task_id.trim().length > 0) {
|
|
2482
|
+
return response.task_id;
|
|
2483
|
+
}
|
|
2484
|
+
return void 0;
|
|
2485
|
+
}
|
|
2486
|
+
buildTriggerRequestBody(call, context, tool, eventId) {
|
|
2487
|
+
const payloadTemplate = isRecord3(tool.trigger.payload_template) ? tool.trigger.payload_template : {};
|
|
2488
|
+
const templateRawData = isRecord3(payloadTemplate.raw_data) ? payloadTemplate.raw_data : {};
|
|
2489
|
+
const payload = {
|
|
2490
|
+
...payloadTemplate,
|
|
2491
|
+
raw_data: {
|
|
2492
|
+
...templateRawData,
|
|
2493
|
+
capability_id: tool.capability_id,
|
|
2494
|
+
tool_name: call.tool_name,
|
|
2495
|
+
tool_call_id: call.tool_call_id,
|
|
2496
|
+
args: call.args,
|
|
2497
|
+
context: {
|
|
2498
|
+
user_id: context.userId,
|
|
2499
|
+
workspace_id: context.workspaceId || null,
|
|
2500
|
+
request_id: context.requestId || null,
|
|
2501
|
+
metadata: context.metadata || null
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
};
|
|
2505
|
+
if (typeof payload.auto_run !== "boolean") {
|
|
2506
|
+
payload.auto_run = false;
|
|
2507
|
+
}
|
|
2508
|
+
return {
|
|
2509
|
+
app_name: this.appName,
|
|
2510
|
+
app_id: this.appId || context.appId,
|
|
2511
|
+
event: tool.trigger.event,
|
|
2512
|
+
event_id: eventId,
|
|
2513
|
+
payload
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
validateToolArgs(tool, args) {
|
|
2517
|
+
const errors = [];
|
|
2518
|
+
if (!isRecord3(args)) {
|
|
2519
|
+
errors.push("args must be an object");
|
|
2520
|
+
return errors;
|
|
2521
|
+
}
|
|
2522
|
+
const requiredFields = Array.isArray(tool.input_schema.required) ? tool.input_schema.required.filter((field) => typeof field === "string") : [];
|
|
2523
|
+
for (const field of requiredFields) {
|
|
2524
|
+
const value = args[field];
|
|
2525
|
+
if (value === void 0 || value === null || typeof value === "string" && value.trim().length === 0) {
|
|
2526
|
+
errors.push(`Missing required field: ${field}`);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
const properties = isRecord3(tool.input_schema.properties) ? tool.input_schema.properties : {};
|
|
2530
|
+
for (const [field, schemaValue] of Object.entries(properties)) {
|
|
2531
|
+
if (!(field in args)) continue;
|
|
2532
|
+
if (!isRecord3(schemaValue)) continue;
|
|
2533
|
+
const typeValue = schemaValue.type;
|
|
2534
|
+
if (typeof typeValue === "string") {
|
|
2535
|
+
if (!this.matchesType(args[field], typeValue)) {
|
|
2536
|
+
errors.push(`Invalid type for ${field}: expected ${typeValue}`);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
if (Array.isArray(typeValue)) {
|
|
2540
|
+
const valid = typeValue.some(
|
|
2541
|
+
(item) => typeof item === "string" && this.matchesType(args[field], item)
|
|
2542
|
+
);
|
|
2543
|
+
if (!valid) {
|
|
2544
|
+
errors.push(
|
|
2545
|
+
`Invalid type for ${field}: expected one of ${typeValue.filter((item) => typeof item === "string").join(", ")}`
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
return errors;
|
|
2551
|
+
}
|
|
2552
|
+
matchesType(value, expectedType) {
|
|
2553
|
+
switch (expectedType) {
|
|
2554
|
+
case "string":
|
|
2555
|
+
return typeof value === "string";
|
|
2556
|
+
case "number":
|
|
2557
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
2558
|
+
case "integer":
|
|
2559
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
2560
|
+
case "boolean":
|
|
2561
|
+
return typeof value === "boolean";
|
|
2562
|
+
case "object":
|
|
2563
|
+
return isRecord3(value);
|
|
2564
|
+
case "array":
|
|
2565
|
+
return Array.isArray(value);
|
|
2566
|
+
case "null":
|
|
2567
|
+
return value === null;
|
|
2568
|
+
default:
|
|
2569
|
+
return true;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
tryEmitLifecycleEvent(input) {
|
|
2573
|
+
if (!input.task_id || !input.event_type) return;
|
|
2574
|
+
const normalized = normalizeContractEvent(input.event_type);
|
|
2575
|
+
if (!normalized || !LIFECYCLE_EVENTS.has(normalized)) {
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2578
|
+
this.lifecycleReporter.emit({
|
|
2579
|
+
tool_call_id: input.tool_call_id,
|
|
2580
|
+
task_id: input.task_id,
|
|
2581
|
+
attempt_id: normalizeAttemptId(input.attempt_id),
|
|
2582
|
+
event_type: normalized,
|
|
2583
|
+
event_id: input.event_id || `${input.task_id}:${normalized}:${Date.now()}`,
|
|
2584
|
+
timestamp: input.timestamp,
|
|
2585
|
+
payload: input.payload
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
toFailedResult(toolCallId, error) {
|
|
2589
|
+
if (error instanceof ContractError) {
|
|
2590
|
+
const retryable = error instanceof RuntimeTransportError ? error.statusCode === void 0 || error.statusCode >= 500 || error.statusCode === 429 : false;
|
|
2591
|
+
return {
|
|
2592
|
+
status: "failed",
|
|
2593
|
+
tool_call_id: toolCallId,
|
|
2594
|
+
error: {
|
|
2595
|
+
code: error.code,
|
|
2596
|
+
message: error.message,
|
|
2597
|
+
retryable
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
if (error instanceof Error) {
|
|
2602
|
+
return {
|
|
2603
|
+
status: "failed",
|
|
2604
|
+
tool_call_id: toolCallId,
|
|
2605
|
+
error: {
|
|
2606
|
+
code: "runtime_error",
|
|
2607
|
+
message: error.message,
|
|
2608
|
+
retryable: true
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
return {
|
|
2613
|
+
status: "failed",
|
|
2614
|
+
tool_call_id: toolCallId,
|
|
2615
|
+
error: {
|
|
2616
|
+
code: "runtime_error",
|
|
2617
|
+
message: "Unknown runtime error",
|
|
2618
|
+
retryable: true
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
};
|
|
2623
|
+
|
|
2624
|
+
// src/core/auth/AuthProvider.ts
|
|
2625
|
+
var StaticAuthProvider = class {
|
|
2626
|
+
constructor(options = {}) {
|
|
2627
|
+
this.appId = options.appId;
|
|
2628
|
+
this.appName = options.appName;
|
|
2629
|
+
this.apiKey = options.apiKey;
|
|
2630
|
+
}
|
|
2631
|
+
buildHeaders(baseHeaders = {}) {
|
|
2632
|
+
const headers = { ...baseHeaders };
|
|
2633
|
+
if (this.apiKey) {
|
|
2634
|
+
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
2635
|
+
}
|
|
2636
|
+
if (this.appId) {
|
|
2637
|
+
headers["x-app-id"] = this.appId;
|
|
2638
|
+
}
|
|
2639
|
+
if (this.appName) {
|
|
2640
|
+
headers["x-app-name"] = this.appName;
|
|
2641
|
+
}
|
|
2642
|
+
return headers;
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
|
|
2646
|
+
// src/adapters/gemini/GeminiToolAdapter.ts
|
|
2647
|
+
function isRecord4(value) {
|
|
2648
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2649
|
+
}
|
|
2650
|
+
function parseArgs(value) {
|
|
2651
|
+
if (isRecord4(value)) return value;
|
|
2652
|
+
if (typeof value === "string") {
|
|
2653
|
+
try {
|
|
2654
|
+
const parsed = JSON.parse(value);
|
|
2655
|
+
return isRecord4(parsed) ? parsed : {};
|
|
2656
|
+
} catch {
|
|
2657
|
+
return {};
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
return {};
|
|
2661
|
+
}
|
|
2662
|
+
var GeminiToolAdapter = class {
|
|
2663
|
+
toProviderTools(tools) {
|
|
2664
|
+
return tools.map((tool) => ({
|
|
2665
|
+
name: tool.tool_name,
|
|
2666
|
+
description: tool.description,
|
|
2667
|
+
parameters: tool.input_schema
|
|
2668
|
+
}));
|
|
2669
|
+
}
|
|
2670
|
+
fromProviderToolCall(call) {
|
|
2671
|
+
return {
|
|
2672
|
+
tool_call_id: call.id,
|
|
2673
|
+
tool_name: call.name,
|
|
2674
|
+
args: parseArgs(call.args)
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
toProviderResult(result) {
|
|
2678
|
+
return {
|
|
2679
|
+
tool_call_id: result.tool_call_id || result.task_id || "unknown",
|
|
2680
|
+
status: result.status,
|
|
2681
|
+
payload: result.status === "failed" ? { error: result.error } : {
|
|
2682
|
+
...result.output || {},
|
|
2683
|
+
task_id: result.task_id,
|
|
2684
|
+
attempt_id: result.attempt_id
|
|
2685
|
+
}
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
};
|
|
2689
|
+
|
|
2690
|
+
// src/adapters/claude/ClaudeToolAdapter.ts
|
|
2691
|
+
var ClaudeToolAdapter = class {
|
|
2692
|
+
toProviderTools(tools) {
|
|
2693
|
+
return tools.map((tool) => ({
|
|
2694
|
+
name: tool.tool_name,
|
|
2695
|
+
description: tool.description,
|
|
2696
|
+
input_schema: tool.input_schema
|
|
2697
|
+
}));
|
|
2698
|
+
}
|
|
2699
|
+
fromProviderToolCall(call) {
|
|
2700
|
+
return {
|
|
2701
|
+
tool_call_id: call.id,
|
|
2702
|
+
tool_name: call.name,
|
|
2703
|
+
args: call.input || {}
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
toProviderResult(result) {
|
|
2707
|
+
const contentPayload = result.status === "failed" ? { error: result.error } : {
|
|
2708
|
+
...result.output || {},
|
|
2709
|
+
task_id: result.task_id,
|
|
2710
|
+
attempt_id: result.attempt_id
|
|
2711
|
+
};
|
|
2712
|
+
return {
|
|
2713
|
+
type: "tool_result",
|
|
2714
|
+
tool_use_id: result.tool_call_id || result.task_id || "unknown",
|
|
2715
|
+
content: JSON.stringify(contentPayload),
|
|
2716
|
+
is_error: result.status === "failed" ? true : void 0
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
|
|
2721
|
+
// src/adapters/codex/CodexToolAdapter.ts
|
|
2722
|
+
function isRecord5(value) {
|
|
2723
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2724
|
+
}
|
|
2725
|
+
function parseArguments(value) {
|
|
2726
|
+
if (isRecord5(value)) return value;
|
|
2727
|
+
if (typeof value === "string") {
|
|
2728
|
+
try {
|
|
2729
|
+
const parsed = JSON.parse(value);
|
|
2730
|
+
return isRecord5(parsed) ? parsed : {};
|
|
2731
|
+
} catch {
|
|
2732
|
+
return {};
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
return {};
|
|
2736
|
+
}
|
|
2737
|
+
var CodexToolAdapter = class {
|
|
2738
|
+
toProviderTools(tools) {
|
|
2739
|
+
return tools.map((tool) => ({
|
|
2740
|
+
type: "function",
|
|
2741
|
+
function: {
|
|
2742
|
+
name: tool.tool_name,
|
|
2743
|
+
description: tool.description,
|
|
2744
|
+
parameters: tool.input_schema
|
|
2745
|
+
}
|
|
2746
|
+
}));
|
|
2747
|
+
}
|
|
2748
|
+
fromProviderToolCall(call) {
|
|
2749
|
+
return {
|
|
2750
|
+
tool_call_id: call.id,
|
|
2751
|
+
tool_name: call.function.name,
|
|
2752
|
+
args: parseArguments(call.function.arguments)
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
toProviderResult(result) {
|
|
2756
|
+
const outputPayload = result.status === "failed" ? { error: result.error } : {
|
|
2757
|
+
...result.output || {},
|
|
2758
|
+
task_id: result.task_id,
|
|
2759
|
+
attempt_id: result.attempt_id
|
|
2760
|
+
};
|
|
2761
|
+
return {
|
|
2762
|
+
tool_call_id: result.tool_call_id || result.task_id || "unknown",
|
|
2763
|
+
output: JSON.stringify(outputPayload),
|
|
2764
|
+
is_error: result.status === "failed" ? true : void 0
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
|
|
2769
|
+
// src/acp/ACPEventMapper.ts
|
|
2770
|
+
function getErrorMessage(error) {
|
|
2771
|
+
if (error instanceof Error) return error.message;
|
|
2772
|
+
if (typeof error === "string") return error;
|
|
2773
|
+
return "Unknown error";
|
|
2774
|
+
}
|
|
2775
|
+
function buildRealtimeXMeta(input) {
|
|
2776
|
+
return {
|
|
2777
|
+
realtimex: {
|
|
2778
|
+
tool_call_id: input.reference.toolCallId,
|
|
2779
|
+
task_id: input.taskId,
|
|
2780
|
+
attempt_id: input.attemptId,
|
|
2781
|
+
event_id: input.eventId,
|
|
2782
|
+
contract_version: "local-app-contract/v1",
|
|
2783
|
+
contract_event: input.contractEvent,
|
|
2784
|
+
cancelled: input.cancelled === true ? true : void 0,
|
|
2785
|
+
app_id: input.reference.appId,
|
|
2786
|
+
user_id: input.reference.userId,
|
|
2787
|
+
workspace_id: input.reference.workspaceId,
|
|
2788
|
+
provider: input.reference.provider,
|
|
2789
|
+
namespace: input.reference.namespace
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
var ACPEventMapper = class {
|
|
2794
|
+
createReference(invocation, context) {
|
|
2795
|
+
return {
|
|
2796
|
+
sessionId: context.sessionId,
|
|
2797
|
+
toolCallId: invocation.toolCallId,
|
|
2798
|
+
appId: context.appId,
|
|
2799
|
+
userId: context.userId,
|
|
2800
|
+
workspaceId: context.workspaceId,
|
|
2801
|
+
provider: context.provider,
|
|
2802
|
+
namespace: context.namespace,
|
|
2803
|
+
title: invocation.title || invocation.toolName,
|
|
2804
|
+
kind: invocation.kind || "execute"
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
buildPendingUpdate(invocation, reference) {
|
|
2808
|
+
return {
|
|
2809
|
+
sessionId: reference.sessionId,
|
|
2810
|
+
update: {
|
|
2811
|
+
type: "tool_call",
|
|
2812
|
+
toolCallId: invocation.toolCallId,
|
|
2813
|
+
title: reference.title,
|
|
2814
|
+
kind: reference.kind,
|
|
2815
|
+
status: "pending",
|
|
2816
|
+
rawInput: invocation.args,
|
|
2817
|
+
_meta: buildRealtimeXMeta({
|
|
2818
|
+
reference,
|
|
2819
|
+
contractEvent: "task.trigger"
|
|
2820
|
+
})
|
|
2821
|
+
}
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
buildResultUpdate(result, reference) {
|
|
2825
|
+
const status = this.mapExecutionResultStatus(result);
|
|
2826
|
+
const taskId = result.task_id;
|
|
2827
|
+
const attemptId = result.attempt_id;
|
|
2828
|
+
const contractEvent = status === "completed" ? "task.completed" : status === "failed" ? "task.failed" : "task.started";
|
|
2829
|
+
const rawOutput = status === "failed" ? {
|
|
2830
|
+
error: result.error || {
|
|
2831
|
+
code: "runtime_error",
|
|
2832
|
+
message: "Unknown runtime error",
|
|
2833
|
+
retryable: true
|
|
2834
|
+
}
|
|
2835
|
+
} : {
|
|
2836
|
+
...result.output || {},
|
|
2837
|
+
task_id: taskId,
|
|
2838
|
+
attempt_id: attemptId
|
|
2839
|
+
};
|
|
2840
|
+
return {
|
|
2841
|
+
sessionId: reference.sessionId,
|
|
2842
|
+
update: {
|
|
2843
|
+
type: "tool_call_update",
|
|
2844
|
+
toolCallId: reference.toolCallId,
|
|
2845
|
+
status,
|
|
2846
|
+
content: [
|
|
2847
|
+
{
|
|
2848
|
+
type: "text",
|
|
2849
|
+
text: status === "in_progress" ? "Task queued" : status === "completed" ? "Task completed" : "Task failed"
|
|
2850
|
+
}
|
|
2851
|
+
],
|
|
2852
|
+
rawOutput,
|
|
2853
|
+
_meta: buildRealtimeXMeta({
|
|
2854
|
+
reference,
|
|
2855
|
+
taskId,
|
|
2856
|
+
attemptId,
|
|
2857
|
+
contractEvent
|
|
2858
|
+
})
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
buildLifecycleUpdate(event, reference) {
|
|
2863
|
+
const status = this.mapContractEventStatus(event.event_type);
|
|
2864
|
+
if (!status) return null;
|
|
2865
|
+
const payload = event.payload || {};
|
|
2866
|
+
const progressText = this.resolveEventText(event.event_type, payload);
|
|
2867
|
+
const cancelled = event.event_type === "task.canceled";
|
|
2868
|
+
return {
|
|
2869
|
+
sessionId: reference.sessionId,
|
|
2870
|
+
update: {
|
|
2871
|
+
type: "tool_call_update",
|
|
2872
|
+
toolCallId: reference.toolCallId,
|
|
2873
|
+
status,
|
|
2874
|
+
content: progressText ? [
|
|
2875
|
+
{
|
|
2876
|
+
type: "text",
|
|
2877
|
+
text: progressText
|
|
2878
|
+
}
|
|
2879
|
+
] : void 0,
|
|
2880
|
+
rawOutput: payload,
|
|
2881
|
+
_meta: buildRealtimeXMeta({
|
|
2882
|
+
reference,
|
|
2883
|
+
taskId: event.task_id,
|
|
2884
|
+
attemptId: event.attempt_id,
|
|
2885
|
+
eventId: event.event_id,
|
|
2886
|
+
contractEvent: event.event_type,
|
|
2887
|
+
cancelled
|
|
2888
|
+
})
|
|
2889
|
+
}
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
buildNotifyFailureUpdate(reference, error) {
|
|
2893
|
+
return {
|
|
2894
|
+
sessionId: reference.sessionId,
|
|
2895
|
+
update: {
|
|
2896
|
+
type: "tool_call_update",
|
|
2897
|
+
toolCallId: reference.toolCallId,
|
|
2898
|
+
status: "failed",
|
|
2899
|
+
content: [
|
|
2900
|
+
{
|
|
2901
|
+
type: "text",
|
|
2902
|
+
text: `Adapter notify failed: ${getErrorMessage(error)}`
|
|
2903
|
+
}
|
|
2904
|
+
],
|
|
2905
|
+
rawOutput: {
|
|
2906
|
+
error: {
|
|
2907
|
+
code: "acp_notify_failed",
|
|
2908
|
+
message: getErrorMessage(error)
|
|
2909
|
+
}
|
|
2910
|
+
},
|
|
2911
|
+
_meta: buildRealtimeXMeta({
|
|
2912
|
+
reference,
|
|
2913
|
+
contractEvent: "adapter.notify_failed"
|
|
2914
|
+
})
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
mapContractEventStatus(eventType) {
|
|
2919
|
+
switch (eventType) {
|
|
2920
|
+
case "task.claimed":
|
|
2921
|
+
case "task.started":
|
|
2922
|
+
case "task.progress":
|
|
2923
|
+
return "in_progress";
|
|
2924
|
+
case "task.completed":
|
|
2925
|
+
return "completed";
|
|
2926
|
+
case "task.failed":
|
|
2927
|
+
case "task.canceled":
|
|
2928
|
+
return "failed";
|
|
2929
|
+
default:
|
|
2930
|
+
return null;
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
mapExecutionResultStatus(result) {
|
|
2934
|
+
switch (result.status) {
|
|
2935
|
+
case "completed":
|
|
2936
|
+
return "completed";
|
|
2937
|
+
case "failed":
|
|
2938
|
+
return "failed";
|
|
2939
|
+
case "queued":
|
|
2940
|
+
return "in_progress";
|
|
2941
|
+
default:
|
|
2942
|
+
return "failed";
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
resolveEventText(eventType, payload) {
|
|
2946
|
+
const payloadMessage = typeof payload.message === "string" ? payload.message : typeof payload.error === "string" ? payload.error : void 0;
|
|
2947
|
+
if (payloadMessage) return payloadMessage;
|
|
2948
|
+
switch (eventType) {
|
|
2949
|
+
case "task.claimed":
|
|
2950
|
+
return "Task claimed";
|
|
2951
|
+
case "task.started":
|
|
2952
|
+
return "Task started";
|
|
2953
|
+
case "task.progress":
|
|
2954
|
+
return "Task in progress";
|
|
2955
|
+
case "task.completed":
|
|
2956
|
+
return "Task completed";
|
|
2957
|
+
case "task.failed":
|
|
2958
|
+
return "Task failed";
|
|
2959
|
+
case "task.canceled":
|
|
2960
|
+
return "Task canceled";
|
|
2961
|
+
default:
|
|
2962
|
+
return void 0;
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
|
|
2967
|
+
// src/acp/ACPPermissionBridge.ts
|
|
2968
|
+
function isRecord6(value) {
|
|
2969
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2970
|
+
}
|
|
2971
|
+
function normalizePermissionOptions(invocation, buildPermissionOptions) {
|
|
2972
|
+
const built = buildPermissionOptions ? buildPermissionOptions(invocation) : void 0;
|
|
2973
|
+
if (built && built.length > 0) return built;
|
|
2974
|
+
return [
|
|
2975
|
+
{
|
|
2976
|
+
id: "allow_once",
|
|
2977
|
+
label: "Allow once",
|
|
2978
|
+
kind: "allow_once"
|
|
2979
|
+
},
|
|
2980
|
+
{
|
|
2981
|
+
id: "deny_once",
|
|
2982
|
+
label: "Deny",
|
|
2983
|
+
kind: "deny_once"
|
|
2984
|
+
}
|
|
2985
|
+
];
|
|
2986
|
+
}
|
|
2987
|
+
function parseDecision(response) {
|
|
2988
|
+
if (typeof response === "boolean") return response;
|
|
2989
|
+
if (!isRecord6(response)) return false;
|
|
2990
|
+
if (typeof response.allow === "boolean") return response.allow;
|
|
2991
|
+
const decision = typeof response.decision === "string" && response.decision || typeof response.outcome === "string" && response.outcome || typeof response.selected === "string" && response.selected || "";
|
|
2992
|
+
const normalized = decision.trim().toLowerCase();
|
|
2993
|
+
if (!normalized) return false;
|
|
2994
|
+
return normalized === "allow" || normalized === "allow_once" || normalized === "allow_always" || normalized === "approved" || normalized === "granted";
|
|
2995
|
+
}
|
|
2996
|
+
var ACPPermissionBridge = class {
|
|
2997
|
+
constructor(options) {
|
|
2998
|
+
this.notifier = options.notifier;
|
|
2999
|
+
this.enabled = options.enabled;
|
|
3000
|
+
this.buildPermissionOptions = options.buildPermissionOptions;
|
|
3001
|
+
}
|
|
3002
|
+
async requestToolPermission(invocation, context) {
|
|
3003
|
+
if (!this.enabled) return true;
|
|
3004
|
+
if (!this.notifier.request) {
|
|
3005
|
+
return false;
|
|
3006
|
+
}
|
|
3007
|
+
const options = normalizePermissionOptions(invocation, this.buildPermissionOptions);
|
|
3008
|
+
try {
|
|
3009
|
+
const response = await this.notifier.request("session/request_permission", {
|
|
3010
|
+
sessionId: context.sessionId,
|
|
3011
|
+
title: invocation.title || invocation.toolName,
|
|
3012
|
+
description: `Allow tool ${invocation.toolName} to run?`,
|
|
3013
|
+
options,
|
|
3014
|
+
metadata: {
|
|
3015
|
+
tool_call_id: invocation.toolCallId,
|
|
3016
|
+
tool_name: invocation.toolName,
|
|
3017
|
+
app_id: context.appId
|
|
3018
|
+
}
|
|
3019
|
+
});
|
|
3020
|
+
return parseDecision(response);
|
|
3021
|
+
} catch {
|
|
3022
|
+
return false;
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
};
|
|
3026
|
+
|
|
3027
|
+
// src/acp/ACPTelemetry.ts
|
|
3028
|
+
var ACPTelemetry = class {
|
|
3029
|
+
constructor(sink) {
|
|
3030
|
+
this.sink = sink;
|
|
3031
|
+
}
|
|
3032
|
+
emit(event) {
|
|
3033
|
+
if (!this.sink) return;
|
|
3034
|
+
try {
|
|
3035
|
+
const output = this.sink.emit(event);
|
|
3036
|
+
if (output && typeof output.then === "function") {
|
|
3037
|
+
void output.catch(() => {
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
} catch {
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
|
|
3045
|
+
// src/acp/ACPContractAdapter.ts
|
|
3046
|
+
function isRecord7(value) {
|
|
3047
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3048
|
+
}
|
|
3049
|
+
function getString(value) {
|
|
3050
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
3051
|
+
}
|
|
3052
|
+
function resolveTaskId(payload) {
|
|
3053
|
+
return getString(payload.task_id) || getString(payload.task_uuid);
|
|
3054
|
+
}
|
|
3055
|
+
function resolveToolCallId2(payload) {
|
|
3056
|
+
const direct = getString(payload.tool_call_id);
|
|
3057
|
+
if (direct) return direct;
|
|
3058
|
+
if (isRecord7(payload.payload)) {
|
|
3059
|
+
const nestedPayload = getString(payload.payload.tool_call_id);
|
|
3060
|
+
if (nestedPayload) return nestedPayload;
|
|
3061
|
+
}
|
|
3062
|
+
if (isRecord7(payload.data)) {
|
|
3063
|
+
const nestedData = getString(payload.data.tool_call_id);
|
|
3064
|
+
if (nestedData) return nestedData;
|
|
3065
|
+
}
|
|
3066
|
+
return void 0;
|
|
3067
|
+
}
|
|
3068
|
+
function isTerminalStatus(status) {
|
|
3069
|
+
return status === "completed" || status === "failed";
|
|
3070
|
+
}
|
|
3071
|
+
var ACPContractAdapter = class {
|
|
3072
|
+
constructor(options) {
|
|
3073
|
+
this.taskReferences = /* @__PURE__ */ new Map();
|
|
3074
|
+
this.toolCallReferences = /* @__PURE__ */ new Map();
|
|
3075
|
+
this.runtime = options.runtime;
|
|
3076
|
+
this.notifier = options.notifier;
|
|
3077
|
+
this.mapper = new ACPEventMapper();
|
|
3078
|
+
this.permissionBridge = new ACPPermissionBridge({
|
|
3079
|
+
notifier: options.notifier,
|
|
3080
|
+
enabled: options.requestPermission === true,
|
|
3081
|
+
buildPermissionOptions: options.buildPermissionOptions
|
|
3082
|
+
});
|
|
3083
|
+
this.telemetry = new ACPTelemetry(options.telemetry);
|
|
3084
|
+
this.runtimeUnsubscribe = this.runtime.onExecutionEvent((event) => {
|
|
3085
|
+
void this.handleRuntimeEvent(event);
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
dispose() {
|
|
3089
|
+
this.runtimeUnsubscribe();
|
|
3090
|
+
this.taskReferences.clear();
|
|
3091
|
+
this.toolCallReferences.clear();
|
|
3092
|
+
}
|
|
3093
|
+
async executeTool(invocation, context) {
|
|
3094
|
+
const reference = this.mapper.createReference(invocation, context);
|
|
3095
|
+
this.rememberReference(reference);
|
|
3096
|
+
await this.notify(this.mapper.buildPendingUpdate(invocation, reference));
|
|
3097
|
+
const permissionGranted = await this.permissionBridge.requestToolPermission(
|
|
3098
|
+
invocation,
|
|
3099
|
+
context
|
|
3100
|
+
);
|
|
3101
|
+
this.telemetry.emit({
|
|
3102
|
+
phase: "permission",
|
|
3103
|
+
result: permissionGranted ? "ok" : "failed",
|
|
3104
|
+
sessionId: context.sessionId,
|
|
3105
|
+
toolCallId: invocation.toolCallId,
|
|
3106
|
+
toolName: invocation.toolName
|
|
3107
|
+
});
|
|
3108
|
+
if (!permissionGranted) {
|
|
3109
|
+
const result2 = {
|
|
3110
|
+
status: "failed",
|
|
3111
|
+
tool_call_id: invocation.toolCallId,
|
|
3112
|
+
error: {
|
|
3113
|
+
code: "permission_denied",
|
|
3114
|
+
message: `Permission denied for ${invocation.toolName}`,
|
|
3115
|
+
retryable: false
|
|
3116
|
+
}
|
|
3117
|
+
};
|
|
3118
|
+
await this.notify(this.mapper.buildResultUpdate(result2, reference));
|
|
3119
|
+
this.cleanupReference(reference, result2);
|
|
3120
|
+
return result2;
|
|
3121
|
+
}
|
|
3122
|
+
const result = await this.runtime.executeToolCall(
|
|
3123
|
+
{
|
|
3124
|
+
tool_call_id: invocation.toolCallId,
|
|
3125
|
+
tool_name: invocation.toolName,
|
|
3126
|
+
args: invocation.args
|
|
3127
|
+
},
|
|
3128
|
+
{
|
|
3129
|
+
appId: context.appId,
|
|
3130
|
+
userId: context.userId,
|
|
3131
|
+
workspaceId: context.workspaceId,
|
|
3132
|
+
provider: context.provider,
|
|
3133
|
+
namespace: context.namespace
|
|
3134
|
+
}
|
|
3135
|
+
);
|
|
3136
|
+
const normalizedResult = {
|
|
3137
|
+
...result,
|
|
3138
|
+
tool_call_id: result.tool_call_id || invocation.toolCallId
|
|
3139
|
+
};
|
|
3140
|
+
if (normalizedResult.task_id) {
|
|
3141
|
+
this.taskReferences.set(normalizedResult.task_id, reference);
|
|
3142
|
+
}
|
|
3143
|
+
await this.notify(this.mapper.buildResultUpdate(normalizedResult, reference));
|
|
3144
|
+
this.telemetry.emit({
|
|
3145
|
+
phase: "execute",
|
|
3146
|
+
result: normalizedResult.status === "failed" ? "failed" : "ok",
|
|
3147
|
+
sessionId: reference.sessionId,
|
|
3148
|
+
toolCallId: reference.toolCallId,
|
|
3149
|
+
toolName: invocation.toolName,
|
|
3150
|
+
taskId: normalizedResult.task_id,
|
|
3151
|
+
attemptId: normalizedResult.attempt_id,
|
|
3152
|
+
status: normalizedResult.status === "queued" ? "in_progress" : normalizedResult.status === "completed" ? "completed" : normalizedResult.status === "failed" ? "failed" : void 0,
|
|
3153
|
+
error: normalizedResult.error?.message
|
|
3154
|
+
});
|
|
3155
|
+
this.cleanupReference(reference, normalizedResult);
|
|
3156
|
+
return normalizedResult;
|
|
3157
|
+
}
|
|
3158
|
+
ingestContractCallback(payload, context) {
|
|
3159
|
+
const initialTaskId = resolveTaskId(payload);
|
|
3160
|
+
const initialToolCallId = resolveToolCallId2(payload);
|
|
3161
|
+
if (initialTaskId) {
|
|
3162
|
+
const existingByTask = this.taskReferences.get(initialTaskId);
|
|
3163
|
+
const existingByCall = initialToolCallId ? this.toolCallReferences.get(initialToolCallId) : void 0;
|
|
3164
|
+
const reference = existingByTask || existingByCall || {
|
|
3165
|
+
sessionId: context.sessionId,
|
|
3166
|
+
toolCallId: initialToolCallId || initialTaskId,
|
|
3167
|
+
appId: "unknown",
|
|
3168
|
+
userId: "unknown",
|
|
3169
|
+
title: initialToolCallId || initialTaskId,
|
|
3170
|
+
kind: "other"
|
|
3171
|
+
};
|
|
3172
|
+
this.taskReferences.set(initialTaskId, reference);
|
|
3173
|
+
this.toolCallReferences.set(reference.toolCallId, reference);
|
|
3174
|
+
}
|
|
3175
|
+
const event = this.runtime.ingestExecutionEvent(payload);
|
|
3176
|
+
this.telemetry.emit({
|
|
3177
|
+
phase: "ingest",
|
|
3178
|
+
result: event ? "ok" : "skipped",
|
|
3179
|
+
sessionId: context.sessionId,
|
|
3180
|
+
toolCallId: event?.tool_call_id,
|
|
3181
|
+
taskId: event?.task_id,
|
|
3182
|
+
attemptId: event?.attempt_id,
|
|
3183
|
+
eventId: event?.event_id,
|
|
3184
|
+
eventType: event?.event_type
|
|
3185
|
+
});
|
|
3186
|
+
return event;
|
|
3187
|
+
}
|
|
3188
|
+
async handleRuntimeEvent(event) {
|
|
3189
|
+
const reference = this.resolveReferenceForEvent(event);
|
|
3190
|
+
if (!reference) {
|
|
3191
|
+
this.telemetry.emit({
|
|
3192
|
+
phase: "runtime_event",
|
|
3193
|
+
result: "skipped",
|
|
3194
|
+
toolCallId: event.tool_call_id,
|
|
3195
|
+
taskId: event.task_id,
|
|
3196
|
+
attemptId: event.attempt_id,
|
|
3197
|
+
eventId: event.event_id,
|
|
3198
|
+
eventType: event.event_type
|
|
3199
|
+
});
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
this.taskReferences.set(event.task_id, reference);
|
|
3203
|
+
const update = this.mapper.buildLifecycleUpdate(event, reference);
|
|
3204
|
+
if (!update) {
|
|
3205
|
+
this.telemetry.emit({
|
|
3206
|
+
phase: "runtime_event",
|
|
3207
|
+
result: "skipped",
|
|
3208
|
+
sessionId: reference.sessionId,
|
|
3209
|
+
toolCallId: reference.toolCallId,
|
|
3210
|
+
taskId: event.task_id,
|
|
3211
|
+
attemptId: event.attempt_id,
|
|
3212
|
+
eventId: event.event_id,
|
|
3213
|
+
eventType: event.event_type
|
|
3214
|
+
});
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
await this.notify(update);
|
|
3218
|
+
this.telemetry.emit({
|
|
3219
|
+
phase: "runtime_event",
|
|
3220
|
+
result: "ok",
|
|
3221
|
+
sessionId: reference.sessionId,
|
|
3222
|
+
toolCallId: reference.toolCallId,
|
|
3223
|
+
taskId: event.task_id,
|
|
3224
|
+
attemptId: event.attempt_id,
|
|
3225
|
+
eventId: event.event_id,
|
|
3226
|
+
eventType: event.event_type,
|
|
3227
|
+
status: update.update.status
|
|
3228
|
+
});
|
|
3229
|
+
if (isTerminalStatus(update.update.status)) {
|
|
3230
|
+
this.taskReferences.delete(event.task_id);
|
|
3231
|
+
this.toolCallReferences.delete(reference.toolCallId);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
resolveReferenceForEvent(event) {
|
|
3235
|
+
const byTask = this.taskReferences.get(event.task_id);
|
|
3236
|
+
if (byTask) return byTask;
|
|
3237
|
+
const byToolCall = this.toolCallReferences.get(event.tool_call_id);
|
|
3238
|
+
if (byToolCall) return byToolCall;
|
|
3239
|
+
return void 0;
|
|
3240
|
+
}
|
|
3241
|
+
rememberReference(reference) {
|
|
3242
|
+
this.toolCallReferences.set(reference.toolCallId, reference);
|
|
3243
|
+
}
|
|
3244
|
+
cleanupReference(reference, result) {
|
|
3245
|
+
const status = result.status === "queued" ? "in_progress" : result.status === "completed" ? "completed" : result.status === "failed" ? "failed" : "failed";
|
|
3246
|
+
if (!isTerminalStatus(status)) return;
|
|
3247
|
+
this.toolCallReferences.delete(reference.toolCallId);
|
|
3248
|
+
if (result.task_id) {
|
|
3249
|
+
this.taskReferences.delete(result.task_id);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
async notify(update) {
|
|
3253
|
+
try {
|
|
3254
|
+
await this.notifier.notify("session/update", update);
|
|
3255
|
+
this.telemetry.emit({
|
|
3256
|
+
phase: "notify",
|
|
3257
|
+
result: "ok",
|
|
3258
|
+
sessionId: update.sessionId,
|
|
3259
|
+
toolCallId: update.update.toolCallId,
|
|
3260
|
+
status: update.update.status,
|
|
3261
|
+
metadata: {
|
|
3262
|
+
type: update.update.type
|
|
3263
|
+
}
|
|
3264
|
+
});
|
|
3265
|
+
} catch (error) {
|
|
3266
|
+
const reference = this.toolCallReferences.get(update.update.toolCallId);
|
|
3267
|
+
this.telemetry.emit({
|
|
3268
|
+
phase: "notify",
|
|
3269
|
+
result: "failed",
|
|
3270
|
+
sessionId: update.sessionId,
|
|
3271
|
+
toolCallId: update.update.toolCallId,
|
|
3272
|
+
status: update.update.status,
|
|
3273
|
+
error: error instanceof Error ? error.message : "Unknown notify error"
|
|
3274
|
+
});
|
|
3275
|
+
if (!reference) return;
|
|
3276
|
+
const failureUpdate = this.mapper.buildNotifyFailureUpdate(reference, error);
|
|
3277
|
+
try {
|
|
3278
|
+
await this.notifier.notify("session/update", failureUpdate);
|
|
3279
|
+
} catch {
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
};
|
|
3284
|
+
|
|
1467
3285
|
// src/index.ts
|
|
1468
3286
|
var _RealtimeXSDK = class _RealtimeXSDK {
|
|
1469
3287
|
constructor(config = {}) {
|
|
@@ -1480,12 +3298,23 @@ var _RealtimeXSDK = class _RealtimeXSDK {
|
|
|
1480
3298
|
this.webhook = new WebhookModule(this.realtimexUrl, this.appName, this.appId, this.apiKey);
|
|
1481
3299
|
this.api = new ApiModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
|
|
1482
3300
|
this.task = new TaskModule(this.realtimexUrl, this.appName, this.appId, this.apiKey);
|
|
3301
|
+
if (config.contract) {
|
|
3302
|
+
this.task.configureContract(config.contract);
|
|
3303
|
+
}
|
|
1483
3304
|
this.port = new PortModule(config.defaultPort);
|
|
1484
3305
|
this.llm = new LLMModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
|
|
1485
3306
|
this.tts = new TTSModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
|
|
1486
3307
|
this.stt = new STTModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
|
|
1487
3308
|
this.agent = new AgentModule(this.httpClient);
|
|
1488
3309
|
this.mcp = new MCPModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
|
|
3310
|
+
this.contract = new ContractModule(this.realtimexUrl, this.appName, this.appId, this.apiKey);
|
|
3311
|
+
this.contractRuntime = new ContractRuntime({
|
|
3312
|
+
baseUrl: this.realtimexUrl,
|
|
3313
|
+
appId: this.appId || void 0,
|
|
3314
|
+
appName: this.appName,
|
|
3315
|
+
apiKey: this.apiKey,
|
|
3316
|
+
permissions: this.permissions
|
|
3317
|
+
});
|
|
1489
3318
|
if (this.permissions.length > 0 && this.appId && !this.apiKey) {
|
|
1490
3319
|
this.register().catch((err) => {
|
|
1491
3320
|
console.error("[RealtimeX SDK] Auto-registration failed:", err.message);
|
|
@@ -1587,20 +3416,59 @@ _RealtimeXSDK.DEFAULT_REALTIMEX_URL = "http://localhost:3001";
|
|
|
1587
3416
|
var RealtimeXSDK = _RealtimeXSDK;
|
|
1588
3417
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1589
3418
|
0 && (module.exports = {
|
|
3419
|
+
ACPContractAdapter,
|
|
3420
|
+
ACPEventMapper,
|
|
3421
|
+
ACPPermissionBridge,
|
|
3422
|
+
ACPTelemetry,
|
|
1590
3423
|
ActivitiesModule,
|
|
1591
3424
|
AgentModule,
|
|
1592
3425
|
ApiModule,
|
|
3426
|
+
CONTRACT_ATTEMPT_PREFIX,
|
|
3427
|
+
CONTRACT_EVENT_ID_HEADER,
|
|
3428
|
+
CONTRACT_SIGNATURE_ALGORITHM,
|
|
3429
|
+
CONTRACT_SIGNATURE_HEADER,
|
|
3430
|
+
ClaudeToolAdapter,
|
|
3431
|
+
CodexToolAdapter,
|
|
3432
|
+
ContractCache,
|
|
3433
|
+
ContractClient,
|
|
3434
|
+
ContractError,
|
|
3435
|
+
ContractHttpClient,
|
|
3436
|
+
ContractModule,
|
|
3437
|
+
ContractRuntime,
|
|
3438
|
+
ContractValidationError,
|
|
3439
|
+
GeminiToolAdapter,
|
|
1593
3440
|
LLMModule,
|
|
1594
3441
|
LLMPermissionError,
|
|
1595
3442
|
LLMProviderError,
|
|
3443
|
+
LOCAL_APP_CONTRACT_VERSION,
|
|
1596
3444
|
MCPModule,
|
|
1597
3445
|
PermissionDeniedError,
|
|
1598
3446
|
PermissionRequiredError,
|
|
1599
3447
|
PortModule,
|
|
1600
3448
|
RealtimeXSDK,
|
|
3449
|
+
RetryPolicy,
|
|
3450
|
+
RuntimeTransportError,
|
|
1601
3451
|
STTModule,
|
|
3452
|
+
ScopeDeniedError,
|
|
3453
|
+
ScopeGuard,
|
|
3454
|
+
StaticAuthProvider,
|
|
1602
3455
|
TTSModule,
|
|
1603
3456
|
TaskModule,
|
|
3457
|
+
ToolNotFoundError,
|
|
3458
|
+
ToolProjector,
|
|
3459
|
+
ToolValidationError,
|
|
1604
3460
|
VectorStore,
|
|
1605
|
-
WebhookModule
|
|
3461
|
+
WebhookModule,
|
|
3462
|
+
buildContractIdempotencyKey,
|
|
3463
|
+
buildContractSignatureMessage,
|
|
3464
|
+
canonicalEventToLegacyAction,
|
|
3465
|
+
createContractEventId,
|
|
3466
|
+
hashContractPayload,
|
|
3467
|
+
normalizeAttemptId,
|
|
3468
|
+
normalizeContractEvent,
|
|
3469
|
+
normalizeLocalAppContractV1,
|
|
3470
|
+
normalizeSchema,
|
|
3471
|
+
parseAttemptRunId,
|
|
3472
|
+
signContractEvent,
|
|
3473
|
+
toStableToolName
|
|
1606
3474
|
});
|