@ct-agents/worker 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/index.ts +3 -217
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ct-agents/worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src"
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"zod": "4.4.3",
|
|
15
|
-
"@ct-agents/environment": "0.
|
|
16
|
-
"@ct-agents/harness": "0.
|
|
17
|
-
"@ct-agents/
|
|
18
|
-
"@ct-agents/
|
|
19
|
-
"@ct-agents/protocol": "0.
|
|
20
|
-
"@ct-agents/tools": "0.
|
|
15
|
+
"@ct-agents/environment": "0.4.0",
|
|
16
|
+
"@ct-agents/harness": "0.4.0",
|
|
17
|
+
"@ct-agents/prompts": "0.4.0",
|
|
18
|
+
"@ct-agents/memory": "0.4.0",
|
|
19
|
+
"@ct-agents/protocol": "0.4.0",
|
|
20
|
+
"@ct-agents/tools": "0.4.0"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
23
|
"dockerode": "4.0.9"
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
ContextResolveQueue,
|
|
3
|
-
ContextResolveQueueClaimInput,
|
|
4
|
-
ContextResolveResult,
|
|
5
|
-
ContextResolveWorkItem,
|
|
6
2
|
EnvironmentId,
|
|
7
3
|
EnvironmentExecutionMode,
|
|
8
4
|
EnvironmentHostedPolicy,
|
|
@@ -46,13 +42,6 @@ export { WorkerHttpError } from './worker-http-transport.js';
|
|
|
46
42
|
export { DEFAULT_WORK_LEASE_RENEW_INTERVAL_MS } from './leased-item-runner.js';
|
|
47
43
|
|
|
48
44
|
export type ResolveResources = (item: ToolWorkItem) => Promise<ResourceSlots> | ResourceSlots;
|
|
49
|
-
export type ResolvePromptContext = (item: ContextResolveWorkItem) =>
|
|
50
|
-
Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
51
|
-
|
|
52
|
-
export type WorkerContextResolveQueue = Pick<
|
|
53
|
-
ContextResolveQueue,
|
|
54
|
-
'claim' | 'renew' | 'complete'
|
|
55
|
-
>;
|
|
56
45
|
|
|
57
46
|
export type ResourceOptionsParser = {
|
|
58
47
|
parse(value: unknown): Record<string, unknown>;
|
|
@@ -132,23 +121,6 @@ export type RunEnvironmentWorkerLoopInput = EnvironmentWorkerInput & {
|
|
|
132
121
|
onError?: (error: unknown) => void | Promise<void>;
|
|
133
122
|
};
|
|
134
123
|
|
|
135
|
-
export type ContextResolverWorkerInput = {
|
|
136
|
-
/** 本地/内存队列可选的不透明路由句柄;HTTP client 已由 Environment Key 绑定。 */
|
|
137
|
-
scope?: ExecutionScope;
|
|
138
|
-
contextResolveQueue: WorkerContextResolveQueue;
|
|
139
|
-
resolvePromptContext: ResolvePromptContext;
|
|
140
|
-
maxBatchSize?: number;
|
|
141
|
-
executionMode?: EnvironmentExecutionMode;
|
|
142
|
-
leaseRenewIntervalMs?: number;
|
|
143
|
-
onError?: (error: unknown) => void | Promise<void>;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export type RunContextResolverWorkerLoopInput = ContextResolverWorkerInput & {
|
|
147
|
-
abortSignal?: AbortSignal;
|
|
148
|
-
idleDelayMs?: number;
|
|
149
|
-
errorDelayMs?: number;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
124
|
export const DEFAULT_CLAIM_BLOCK_MS = 15_000;
|
|
153
125
|
const PLATFORM_PROXY_RESOURCE_CAPABILITIES = [
|
|
154
126
|
{ slot: 'skills', implementationId: 'platform-skills-store' },
|
|
@@ -486,98 +458,6 @@ export class EnvironmentWorker {
|
|
|
486
458
|
|
|
487
459
|
}
|
|
488
460
|
|
|
489
|
-
/**
|
|
490
|
-
* 独立 context resolver worker。
|
|
491
|
-
*
|
|
492
|
-
* 它只处理 context.resolve 工作项,把接入方或平台托管 Resource 物化成 prompt snapshot。
|
|
493
|
-
* 该闭环不执行 tool handler,也不会写 tool.called/tool-result 事件。
|
|
494
|
-
*/
|
|
495
|
-
export class ContextResolverWorker {
|
|
496
|
-
public constructor(private readonly input: ContextResolverWorkerInput) {}
|
|
497
|
-
|
|
498
|
-
public async drainOnce(): Promise<number> {
|
|
499
|
-
const executionMode = this.input.executionMode ?? 'self_hosted';
|
|
500
|
-
const items = await this.input.contextResolveQueue.claim({
|
|
501
|
-
scope: this.input.scope ?? {},
|
|
502
|
-
executionMode,
|
|
503
|
-
max: this.input.maxBatchSize ?? 1,
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
await Promise.all(items.map((item) => this.handleItem(item)));
|
|
507
|
-
return items.length;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
private async handleItem(item: ContextResolveWorkItem): Promise<void> {
|
|
511
|
-
const renew = this.input.contextResolveQueue.renew;
|
|
512
|
-
const leaseId = item.leaseId;
|
|
513
|
-
await runLeasedItem<ContextResolveResult>({
|
|
514
|
-
...(leaseId && renew ? {
|
|
515
|
-
renewal: {
|
|
516
|
-
intervalMs: this.input.leaseRenewIntervalMs,
|
|
517
|
-
renew: () => renew({
|
|
518
|
-
workId: item.workId,
|
|
519
|
-
leaseId,
|
|
520
|
-
scope: item.scope,
|
|
521
|
-
executionMode: this.input.executionMode ?? item.executionMode ?? 'self_hosted',
|
|
522
|
-
}),
|
|
523
|
-
onError: this.input.onError,
|
|
524
|
-
},
|
|
525
|
-
} : {}),
|
|
526
|
-
execute: async () => {
|
|
527
|
-
try {
|
|
528
|
-
return {
|
|
529
|
-
workId: item.workId,
|
|
530
|
-
...(item.leaseId ? { leaseId: item.leaseId } : {}),
|
|
531
|
-
status: 'completed',
|
|
532
|
-
snapshot: await this.input.resolvePromptContext(item),
|
|
533
|
-
};
|
|
534
|
-
} catch (error) {
|
|
535
|
-
return {
|
|
536
|
-
workId: item.workId,
|
|
537
|
-
...(item.leaseId ? { leaseId: item.leaseId } : {}),
|
|
538
|
-
status: 'failed',
|
|
539
|
-
error: {
|
|
540
|
-
message: normalizeContextResolverError(error),
|
|
541
|
-
retryable: false,
|
|
542
|
-
},
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
},
|
|
546
|
-
complete: (result) => this.input.contextResolveQueue.complete(
|
|
547
|
-
result,
|
|
548
|
-
this.buildCompleteScope(item),
|
|
549
|
-
),
|
|
550
|
-
completeRejectedError: () => new Error(`context work complete 未被接受:${item.workId}`),
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
private buildCompleteScope(item: ContextResolveWorkItem) {
|
|
555
|
-
return {
|
|
556
|
-
scope: item.scope,
|
|
557
|
-
executionMode: this.input.executionMode ?? item.executionMode ?? 'self_hosted',
|
|
558
|
-
leaseId: item.leaseId,
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
export async function runContextResolverWorkerLoop(input: RunContextResolverWorkerLoopInput): Promise<void> {
|
|
564
|
-
const worker = new ContextResolverWorker(input);
|
|
565
|
-
const idleDelayMs = input.idleDelayMs ?? 250;
|
|
566
|
-
const errorDelayMs = input.errorDelayMs ?? 1000;
|
|
567
|
-
|
|
568
|
-
while (!input.abortSignal?.aborted) {
|
|
569
|
-
try {
|
|
570
|
-
const drained = await worker.drainOnce();
|
|
571
|
-
if (drained === 0) {
|
|
572
|
-
await delay(idleDelayMs, input.abortSignal);
|
|
573
|
-
}
|
|
574
|
-
} catch (error) {
|
|
575
|
-
await input.onError?.(error);
|
|
576
|
-
await delay(errorDelayMs, input.abortSignal);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
461
|
export async function runEnvironmentWorkerLoop(input: RunEnvironmentWorkerLoopInput): Promise<void> {
|
|
582
462
|
const worker = new EnvironmentWorker(input);
|
|
583
463
|
const idleDelayMs = input.idleDelayMs ?? 250;
|
|
@@ -640,8 +520,6 @@ export type HttpWorkQueueClientInput = {
|
|
|
640
520
|
signal?: AbortSignal;
|
|
641
521
|
};
|
|
642
522
|
|
|
643
|
-
export type HttpContextResolveQueueClientInput = Omit<HttpWorkQueueClientInput, 'workerInstanceId'>;
|
|
644
|
-
|
|
645
523
|
export function zodObjectToJsonSchema(schema: z.ZodType<unknown>): Record<string, unknown> {
|
|
646
524
|
const jsonSchema = z.toJSONSchema(schema) as Record<string, unknown>;
|
|
647
525
|
const normalized = normalizeCatalogJsonSchema(jsonSchema, true);
|
|
@@ -738,6 +616,7 @@ export class HttpWorkQueueClient implements WorkerWorkQueue {
|
|
|
738
616
|
}
|
|
739
617
|
const data = await this.transport.requestJson('/work/complete', {
|
|
740
618
|
method: 'POST',
|
|
619
|
+
signal: this.input.signal,
|
|
741
620
|
body: JSON.stringify({
|
|
742
621
|
...result,
|
|
743
622
|
leaseId: scope.leaseId,
|
|
@@ -768,13 +647,13 @@ export class HttpWorkQueueClient implements WorkerWorkQueue {
|
|
|
768
647
|
};
|
|
769
648
|
}
|
|
770
649
|
|
|
771
|
-
public async heartbeat(workerInstanceId: string) {
|
|
650
|
+
public async heartbeat(workerInstanceId: string, signal = this.input.signal) {
|
|
772
651
|
if (workerInstanceId !== this.input.workerInstanceId) {
|
|
773
652
|
throw new Error('Worker 心跳身份与 client 实例不一致');
|
|
774
653
|
}
|
|
775
654
|
const data = await this.transport.requestJson(`/instances/${encodeURIComponent(workerInstanceId)}/heartbeat`, {
|
|
776
655
|
method: 'POST',
|
|
777
|
-
signal
|
|
656
|
+
signal,
|
|
778
657
|
}, '平台工作队列响应不是 JSON 对象');
|
|
779
658
|
const record = expectRecord(data, 'Worker 心跳响应缺少 data');
|
|
780
659
|
return {
|
|
@@ -800,54 +679,6 @@ export class HttpWorkQueueClient implements WorkerWorkQueue {
|
|
|
800
679
|
|
|
801
680
|
}
|
|
802
681
|
|
|
803
|
-
export class HttpContextResolveQueueClient implements WorkerContextResolveQueue {
|
|
804
|
-
private readonly transport: WorkerHttpTransport;
|
|
805
|
-
|
|
806
|
-
public constructor(private readonly input: HttpContextResolveQueueClientInput) {
|
|
807
|
-
this.transport = new WorkerHttpTransport(input);
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
public async claim(input: ContextResolveQueueClaimInput) {
|
|
811
|
-
const data = await this.transport.requestJson('/context-work/claim', {
|
|
812
|
-
method: 'POST',
|
|
813
|
-
signal: this.input.signal,
|
|
814
|
-
body: JSON.stringify({
|
|
815
|
-
max: input.max,
|
|
816
|
-
}),
|
|
817
|
-
}, '平台 context 工作队列响应不是 JSON 对象');
|
|
818
|
-
|
|
819
|
-
const record = expectRecord(data, 'context claim 响应缺少 data');
|
|
820
|
-
const items = record.items;
|
|
821
|
-
if (!Array.isArray(items)) {
|
|
822
|
-
throw new Error('context claim 响应缺少 items 数组');
|
|
823
|
-
}
|
|
824
|
-
return items.map(parseContextResolveWorkItem);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
public async complete(result: ContextResolveResult): Promise<boolean> {
|
|
828
|
-
const data = await this.transport.requestJson('/context-work/complete', {
|
|
829
|
-
method: 'POST',
|
|
830
|
-
body: JSON.stringify(result),
|
|
831
|
-
}, '平台 context 工作队列响应不是 JSON 对象');
|
|
832
|
-
const record = expectRecord(data, 'context complete 响应缺少 data');
|
|
833
|
-
if (record.accepted !== true) {
|
|
834
|
-
throw new Error(`context complete 未被接受:${result.workId}`);
|
|
835
|
-
}
|
|
836
|
-
return true;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
public async renew(input: { workId: string; leaseId: string }): Promise<boolean> {
|
|
840
|
-
const data = await this.transport.requestJson('/context-work/renew', {
|
|
841
|
-
method: 'POST',
|
|
842
|
-
signal: this.input.signal,
|
|
843
|
-
body: JSON.stringify({ workId: input.workId, leaseId: input.leaseId }),
|
|
844
|
-
}, '平台 context 工作队列响应不是 JSON 对象');
|
|
845
|
-
const record = expectRecord(data, 'context renew 响应缺少 data');
|
|
846
|
-
return record.renewed === true;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
}
|
|
850
|
-
|
|
851
682
|
function toToolExecutionResult(
|
|
852
683
|
workId: string,
|
|
853
684
|
execution: ToolExecution<unknown>,
|
|
@@ -1160,44 +991,6 @@ function parseExecutionScope(value: unknown): ExecutionScope {
|
|
|
1160
991
|
return Object.fromEntries(entries);
|
|
1161
992
|
}
|
|
1162
993
|
|
|
1163
|
-
function parseContextResolveWorkItem(value: unknown): ContextResolveWorkItem {
|
|
1164
|
-
const record = expectRecord(value, 'context work item 不是对象');
|
|
1165
|
-
const workId = expectString(record.workId, 'workId');
|
|
1166
|
-
const leaseId = typeof record.leaseId === 'string' && record.leaseId.trim()
|
|
1167
|
-
? record.leaseId.trim()
|
|
1168
|
-
: undefined;
|
|
1169
|
-
const sessionId = expectString(record.sessionId, 'sessionId');
|
|
1170
|
-
const rootSessionId = expectString(record.rootSessionId, 'rootSessionId');
|
|
1171
|
-
const request = expectRecord(record.request, 'request 必须是对象');
|
|
1172
|
-
const kind = expectString(request.kind, 'request.kind');
|
|
1173
|
-
if (kind !== 'prompt_context') {
|
|
1174
|
-
throw new Error('request.kind 必须是 prompt_context');
|
|
1175
|
-
}
|
|
1176
|
-
const rawKeys = request.keys;
|
|
1177
|
-
const keys = Array.isArray(rawKeys)
|
|
1178
|
-
? rawKeys
|
|
1179
|
-
.filter((key): key is string => typeof key === 'string')
|
|
1180
|
-
.map((key) => key.trim())
|
|
1181
|
-
.filter(Boolean)
|
|
1182
|
-
: [];
|
|
1183
|
-
if (!Array.isArray(rawKeys) || keys.length === 0 || keys.length !== rawKeys.length) {
|
|
1184
|
-
throw new Error('request.keys 必须是非空字符串数组');
|
|
1185
|
-
}
|
|
1186
|
-
const executionMode = parseExecutionMode(record.executionMode);
|
|
1187
|
-
return {
|
|
1188
|
-
workId,
|
|
1189
|
-
...(leaseId ? { leaseId } : {}),
|
|
1190
|
-
sessionId,
|
|
1191
|
-
rootSessionId,
|
|
1192
|
-
scope: parseExecutionScope(record.scope),
|
|
1193
|
-
request: {
|
|
1194
|
-
kind: 'prompt_context',
|
|
1195
|
-
keys,
|
|
1196
|
-
},
|
|
1197
|
-
...(executionMode ? { executionMode } : {}),
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
994
|
function normalizeWorkerError(error: unknown): string {
|
|
1202
995
|
if (error instanceof Error) {
|
|
1203
996
|
return `工具执行失败:${error.message}`;
|
|
@@ -1222,13 +1015,6 @@ function toWorkerErrorPayload(error: unknown): NonNullable<ToolExecutionResult['
|
|
|
1222
1015
|
};
|
|
1223
1016
|
}
|
|
1224
1017
|
|
|
1225
|
-
function normalizeContextResolverError(error: unknown): string {
|
|
1226
|
-
if (error instanceof Error) {
|
|
1227
|
-
return `context resolve 失败:${error.message}`;
|
|
1228
|
-
}
|
|
1229
|
-
return `context resolve 失败:${String(error)}`;
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
1018
|
function expectString(value: unknown, field: string): string {
|
|
1233
1019
|
if (typeof value !== 'string' || !value.trim()) {
|
|
1234
1020
|
throw new Error(`字段 ${field} 必须是非空字符串`);
|