pi-long-task 0.1.3 → 0.3.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/README.md +39 -8
- package/package.json +1 -1
- package/src/coordinator.ts +222 -15
- package/src/index.ts +64 -0
- package/src/render.ts +287 -9
- package/src/task_progress.ts +187 -0
- package/src/types.ts +3 -0
- package/src/worker_session.ts +142 -2
package/src/worker_session.ts
CHANGED
|
@@ -201,7 +201,7 @@ export interface WorkerSessionLike {
|
|
|
201
201
|
compact?(customInstructions?: string): Promise<unknown>;
|
|
202
202
|
dispose?(): void;
|
|
203
203
|
getLastAssistantText?(): string | undefined;
|
|
204
|
-
getSessionStats?(): unknown
|
|
204
|
+
getSessionStats?(): unknown | Promise<unknown>;
|
|
205
205
|
getContextUsage?(): unknown;
|
|
206
206
|
sessionFile?: string;
|
|
207
207
|
sessionId?: string;
|
|
@@ -246,6 +246,8 @@ export interface CapturedWorkerEvent {
|
|
|
246
246
|
toolName?: string;
|
|
247
247
|
isError?: boolean;
|
|
248
248
|
note?: string;
|
|
249
|
+
usageCostTotal?: number;
|
|
250
|
+
usageCostKey?: string;
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
export interface SessionOutcome {
|
|
@@ -261,6 +263,8 @@ export interface SessionOutcome {
|
|
|
261
263
|
contextObservations: string[];
|
|
262
264
|
compactionEvents: string[];
|
|
263
265
|
events: CapturedWorkerEvent[];
|
|
266
|
+
workerCostTotal: number;
|
|
267
|
+
workerCostSource?: string;
|
|
264
268
|
shutdownRequested: boolean;
|
|
265
269
|
timedOut: boolean;
|
|
266
270
|
aborted: boolean;
|
|
@@ -345,6 +349,9 @@ export async function runWorkerTask(options: RunWorkerTaskOptions): Promise<Sess
|
|
|
345
349
|
let error: string | undefined;
|
|
346
350
|
let finished = false;
|
|
347
351
|
let turnCount = 0;
|
|
352
|
+
let messageUsageCostTotal = 0;
|
|
353
|
+
let hasMessageUsageCost = false;
|
|
354
|
+
let sessionStatsCostTotal: number | undefined;
|
|
348
355
|
|
|
349
356
|
const prompt = buildTaskPrompt(options);
|
|
350
357
|
const taskTimeoutSeconds = options.taskTimeoutSeconds ?? DEFAULT_TASK_TIMEOUT_SECONDS;
|
|
@@ -366,6 +373,22 @@ export async function runWorkerTask(options: RunWorkerTaskOptions): Promise<Sess
|
|
|
366
373
|
timers.clear();
|
|
367
374
|
};
|
|
368
375
|
|
|
376
|
+
const messageUsageCostsByKey = new Map<string, number>();
|
|
377
|
+
const recordWorkerUsageCost = (cost: number | undefined, key: string | undefined) => {
|
|
378
|
+
if (cost === undefined) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
hasMessageUsageCost = true;
|
|
382
|
+
if (!key) {
|
|
383
|
+
messageUsageCostTotal += cost;
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const previousCost = messageUsageCostsByKey.get(key) ?? 0;
|
|
388
|
+
messageUsageCostsByKey.set(key, cost);
|
|
389
|
+
messageUsageCostTotal += cost - previousCost;
|
|
390
|
+
};
|
|
391
|
+
|
|
369
392
|
const schedule = (fn: () => void | Promise<void>, ms: number) => {
|
|
370
393
|
const timer = setTimeout(() => {
|
|
371
394
|
timers.delete(timer);
|
|
@@ -458,6 +481,7 @@ export async function runWorkerTask(options: RunWorkerTaskOptions): Promise<Sess
|
|
|
458
481
|
if (messageText) {
|
|
459
482
|
assistantText = messageText;
|
|
460
483
|
}
|
|
484
|
+
recordWorkerUsageCost(workerUsageCostFromEvent(event), workerUsageCostKeyFromEvent(event));
|
|
461
485
|
break;
|
|
462
486
|
}
|
|
463
487
|
case "turn_end": {
|
|
@@ -538,6 +562,7 @@ export async function runWorkerTask(options: RunWorkerTaskOptions): Promise<Sess
|
|
|
538
562
|
assistantText = latestAssistantText(session, assistantText);
|
|
539
563
|
sessionFile = session.sessionFile ?? sessionFile;
|
|
540
564
|
sessionId = session.sessionId ?? sessionId;
|
|
565
|
+
sessionStatsCostTotal = await workerUsageCostFromSessionStats(session);
|
|
541
566
|
session.dispose?.();
|
|
542
567
|
}
|
|
543
568
|
}
|
|
@@ -547,6 +572,10 @@ export async function runWorkerTask(options: RunWorkerTaskOptions): Promise<Sess
|
|
|
547
572
|
}
|
|
548
573
|
|
|
549
574
|
const reportedStatus = parseReportedStatus(assistantText);
|
|
575
|
+
const capturedWorkerCost = selectWorkerCostTotal({
|
|
576
|
+
messageCostTotal: hasMessageUsageCost ? messageUsageCostTotal : undefined,
|
|
577
|
+
statsCostTotal: sessionStatsCostTotal,
|
|
578
|
+
});
|
|
550
579
|
return {
|
|
551
580
|
task: options.task,
|
|
552
581
|
attempt: options.attempt,
|
|
@@ -560,6 +589,8 @@ export async function runWorkerTask(options: RunWorkerTaskOptions): Promise<Sess
|
|
|
560
589
|
contextObservations,
|
|
561
590
|
compactionEvents,
|
|
562
591
|
events,
|
|
592
|
+
workerCostTotal: capturedWorkerCost.total,
|
|
593
|
+
workerCostSource: capturedWorkerCost.source,
|
|
563
594
|
shutdownRequested,
|
|
564
595
|
timedOut,
|
|
565
596
|
aborted: aborted || Boolean(options.abortSignal?.aborted),
|
|
@@ -723,9 +754,15 @@ function summarizeWorkerEvent(event: unknown): CapturedWorkerEvent | undefined {
|
|
|
723
754
|
};
|
|
724
755
|
}
|
|
725
756
|
|
|
757
|
+
if (event.type === "message_end") {
|
|
758
|
+
const usageCostTotal = workerUsageCostFromEvent(event);
|
|
759
|
+
return usageCostTotal === undefined
|
|
760
|
+
? { type: event.type }
|
|
761
|
+
: { type: event.type, usageCostTotal, usageCostKey: workerUsageCostKeyFromEvent(event) };
|
|
762
|
+
}
|
|
763
|
+
|
|
726
764
|
if (
|
|
727
765
|
event.type === "turn_end" ||
|
|
728
|
-
event.type === "message_end" ||
|
|
729
766
|
event.type === "compaction_start" ||
|
|
730
767
|
event.type === "compaction_end" ||
|
|
731
768
|
event.type === "agent_end" ||
|
|
@@ -738,6 +775,109 @@ function summarizeWorkerEvent(event: unknown): CapturedWorkerEvent | undefined {
|
|
|
738
775
|
return undefined;
|
|
739
776
|
}
|
|
740
777
|
|
|
778
|
+
export function workerUsageCostFromEvent(event: unknown): number | undefined {
|
|
779
|
+
if (!isRecord(event)) {
|
|
780
|
+
return undefined;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
for (const candidate of [event.assistantMessage, event.message, event]) {
|
|
784
|
+
const cost = workerUsageCostFromAssistantMessage(candidate);
|
|
785
|
+
if (cost !== undefined) {
|
|
786
|
+
return cost;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
export function workerUsageCostFromAssistantMessage(message: unknown): number | undefined {
|
|
793
|
+
if (!isRecord(message)) {
|
|
794
|
+
return undefined;
|
|
795
|
+
}
|
|
796
|
+
return usageCostTotal(message.usage);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export function workerUsageCostKeyFromEvent(event: unknown): string | undefined {
|
|
800
|
+
if (!isRecord(event)) {
|
|
801
|
+
return undefined;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
for (const candidate of [event.assistantMessage, event.message, event]) {
|
|
805
|
+
const key = workerUsageCostKeyFromAssistantMessage(candidate);
|
|
806
|
+
if (key) {
|
|
807
|
+
return key;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return undefined;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function workerUsageCostKeyFromAssistantMessage(message: unknown): string | undefined {
|
|
814
|
+
if (!isRecord(message)) {
|
|
815
|
+
return undefined;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
for (const keyName of ["id", "messageId", "uuid"] as const) {
|
|
819
|
+
const value = message[keyName];
|
|
820
|
+
if (typeof value === "string" && value) {
|
|
821
|
+
return `${keyName}:${value}`;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
export function workerUsageCostFromStats(stats: unknown): number | undefined {
|
|
828
|
+
if (!isRecord(stats)) {
|
|
829
|
+
return undefined;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const directCost = finiteNonNegativeNumber(stats.cost);
|
|
833
|
+
if (directCost !== undefined) {
|
|
834
|
+
return directCost;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return usageCostTotal(stats.usage) ?? usageCostTotal(stats);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async function workerUsageCostFromSessionStats(session: WorkerSessionLike): Promise<number | undefined> {
|
|
841
|
+
if (!session.getSessionStats) {
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
try {
|
|
846
|
+
return workerUsageCostFromStats(await session.getSessionStats());
|
|
847
|
+
} catch {
|
|
848
|
+
return undefined;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function usageCostTotal(usage: unknown): number | undefined {
|
|
853
|
+
if (!isRecord(usage)) {
|
|
854
|
+
return undefined;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const cost = usage.cost;
|
|
858
|
+
if (isRecord(cost)) {
|
|
859
|
+
return finiteNonNegativeNumber(cost.total);
|
|
860
|
+
}
|
|
861
|
+
return finiteNonNegativeNumber(cost);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function selectWorkerCostTotal(options: { messageCostTotal: number | undefined; statsCostTotal: number | undefined }): {
|
|
865
|
+
total: number;
|
|
866
|
+
source?: string;
|
|
867
|
+
} {
|
|
868
|
+
if (options.statsCostTotal !== undefined) {
|
|
869
|
+
return { total: options.statsCostTotal, source: "session_stats" };
|
|
870
|
+
}
|
|
871
|
+
if (options.messageCostTotal !== undefined) {
|
|
872
|
+
return { total: options.messageCostTotal, source: "message_end" };
|
|
873
|
+
}
|
|
874
|
+
return { total: 0 };
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function finiteNonNegativeNumber(value: unknown): number | undefined {
|
|
878
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
879
|
+
}
|
|
880
|
+
|
|
741
881
|
function captureContextUsage(
|
|
742
882
|
session: WorkerSessionLike | undefined,
|
|
743
883
|
turnCount: number,
|