@purista/harness 1.2.2 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/harness/defineHarness.d.ts +19 -2
- package/dist/models/registry.d.ts +10 -3
- package/dist/sessions/index.js +124 -6
- package/package.json +1 -1
|
@@ -552,7 +552,15 @@ export interface RunSummary {
|
|
|
552
552
|
agentCalls: number;
|
|
553
553
|
error?: SerializedError;
|
|
554
554
|
}
|
|
555
|
-
/**
|
|
555
|
+
/**
|
|
556
|
+
* Harness streaming events emitted from `session.workflows.<id>.stream(...)`.
|
|
557
|
+
*
|
|
558
|
+
* `text(...)` and `object(...)` model calls return final results and do not
|
|
559
|
+
* expose partial output. Consumed model streams are private by default.
|
|
560
|
+
* `model.delta`, `model.object.partial`, and streamed `model.object` are
|
|
561
|
+
* emitted only when that `textStream(...)` or `objectStream(...)` call passes
|
|
562
|
+
* `{ emitRunEvents: true }`.
|
|
563
|
+
*/
|
|
556
564
|
export type RunEvent = {
|
|
557
565
|
type: 'run.started';
|
|
558
566
|
runId: string;
|
|
@@ -578,7 +586,10 @@ export type RunEvent = {
|
|
|
578
586
|
} | {
|
|
579
587
|
type: 'model.delta';
|
|
580
588
|
runId: string;
|
|
581
|
-
|
|
589
|
+
streamId: string;
|
|
590
|
+
agentId?: string;
|
|
591
|
+
workflowId?: string;
|
|
592
|
+
modelAlias?: string;
|
|
582
593
|
delta: string;
|
|
583
594
|
} | {
|
|
584
595
|
type: 'tool.started';
|
|
@@ -603,12 +614,18 @@ export type RunEvent = {
|
|
|
603
614
|
} | {
|
|
604
615
|
type: 'model.object.partial';
|
|
605
616
|
runId: string;
|
|
617
|
+
streamId: string;
|
|
606
618
|
agentId?: string;
|
|
619
|
+
workflowId?: string;
|
|
620
|
+
modelAlias?: string;
|
|
607
621
|
partial: JsonValue;
|
|
608
622
|
} | {
|
|
609
623
|
type: 'model.object';
|
|
610
624
|
runId: string;
|
|
611
625
|
agentId?: string;
|
|
626
|
+
workflowId?: string;
|
|
627
|
+
modelAlias?: string;
|
|
628
|
+
streamId?: string;
|
|
612
629
|
object: JsonValue;
|
|
613
630
|
usage?: TokenUsage;
|
|
614
631
|
} | {
|
|
@@ -2,11 +2,18 @@ import type { EmbeddingRequest, EmbeddingResponse, ContentPart, ModelAlias, Mode
|
|
|
2
2
|
import type { TelemetryShim } from '../telemetry/index.js';
|
|
3
3
|
import type { JsonValue } from './json.js';
|
|
4
4
|
export interface ModelInvokeContext {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/** Harness instance name used for telemetry and run-event attribution. */
|
|
6
|
+
harnessName?: string;
|
|
7
|
+
/** Session id used for telemetry and run-event attribution. */
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
/** Run id used for telemetry and run-event attribution. */
|
|
10
|
+
runId?: string;
|
|
11
|
+
/** Workflow id when the model call belongs to a workflow run. */
|
|
8
12
|
workflowId?: string;
|
|
13
|
+
/** Agent id when the model call belongs to an agent run. */
|
|
9
14
|
agentId?: string;
|
|
15
|
+
/** Mirrors consumed stream chunks into the enclosing session `RunEvent` stream. Defaults to false. */
|
|
16
|
+
emitRunEvents?: boolean;
|
|
10
17
|
}
|
|
11
18
|
type TextPart = Extract<ContentPart, {
|
|
12
19
|
kind: 'text';
|
package/dist/sessions/index.js
CHANGED
|
@@ -433,7 +433,12 @@ export function createSessionHarness(definition) {
|
|
|
433
433
|
input,
|
|
434
434
|
history: await definition.state.listMessages(sessionId),
|
|
435
435
|
agent,
|
|
436
|
-
models: modelRegistry,
|
|
436
|
+
models: withRunEventModelRegistry(modelRegistry, {
|
|
437
|
+
harnessName: definition.name,
|
|
438
|
+
sessionId,
|
|
439
|
+
runId,
|
|
440
|
+
agentId
|
|
441
|
+
}, emit),
|
|
437
442
|
skills: resolvedSkills,
|
|
438
443
|
customTools: definition.tools,
|
|
439
444
|
mcpRegistry,
|
|
@@ -585,7 +590,12 @@ export function createSessionHarness(definition) {
|
|
|
585
590
|
signal: runSignal.signal,
|
|
586
591
|
runId,
|
|
587
592
|
sessionId,
|
|
588
|
-
models: modelRegistry,
|
|
593
|
+
models: withRunEventModelRegistry(modelRegistry, {
|
|
594
|
+
harnessName: definition.name,
|
|
595
|
+
sessionId,
|
|
596
|
+
runId,
|
|
597
|
+
workflowId
|
|
598
|
+
}, emit),
|
|
589
599
|
metadata: opts?.metadata ?? {},
|
|
590
600
|
metrics: workflowMetrics,
|
|
591
601
|
memory,
|
|
@@ -615,7 +625,13 @@ export function createSessionHarness(definition) {
|
|
|
615
625
|
input: agentInput,
|
|
616
626
|
history: await definition.state.listMessages(sessionId),
|
|
617
627
|
agent: agent,
|
|
618
|
-
models: modelRegistry,
|
|
628
|
+
models: withRunEventModelRegistry(modelRegistry, {
|
|
629
|
+
harnessName: definition.name,
|
|
630
|
+
sessionId,
|
|
631
|
+
runId,
|
|
632
|
+
workflowId,
|
|
633
|
+
agentId
|
|
634
|
+
}, emit),
|
|
619
635
|
skills: resolvedSkills,
|
|
620
636
|
customTools: definition.tools,
|
|
621
637
|
mcpRegistry,
|
|
@@ -764,6 +780,100 @@ export function createSessionHarness(definition) {
|
|
|
764
780
|
}
|
|
765
781
|
}
|
|
766
782
|
}
|
|
783
|
+
function withRunEventModelRegistry(models, context, emitEvent) {
|
|
784
|
+
return Object.fromEntries(Object.entries(models).map(([alias, handle]) => [alias, withRunEventModelHandle(alias, handle, context, emitEvent)]));
|
|
785
|
+
}
|
|
786
|
+
function withRunEventModelHandle(alias, handle, context, emitEvent) {
|
|
787
|
+
if (!handle || typeof handle !== 'object')
|
|
788
|
+
return handle;
|
|
789
|
+
const source = handle;
|
|
790
|
+
const wrapped = { ...source };
|
|
791
|
+
for (const method of ['text', 'object', 'embed', 'rerank']) {
|
|
792
|
+
const fn = source[method];
|
|
793
|
+
if (typeof fn !== 'function')
|
|
794
|
+
continue;
|
|
795
|
+
wrapped[method] = (req, signal, ctx) => fn.call(source, req, signal, mergeModelRunContext(context, ctx));
|
|
796
|
+
}
|
|
797
|
+
const textStream = source['textStream'];
|
|
798
|
+
if (typeof textStream === 'function') {
|
|
799
|
+
wrapped['textStream'] = (req, signal, ctx) => {
|
|
800
|
+
const streamContext = modelStreamRunContext(context, ctx, alias);
|
|
801
|
+
return emitTextStreamRunEvents(textStream.call(source, req, signal, streamContext), streamContext, emitEvent);
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
const objectStream = source['objectStream'];
|
|
805
|
+
if (typeof objectStream === 'function') {
|
|
806
|
+
wrapped['objectStream'] = (req, signal, ctx) => {
|
|
807
|
+
const streamContext = modelStreamRunContext(context, ctx, alias);
|
|
808
|
+
return emitObjectStreamRunEvents(objectStream.call(source, req, signal, streamContext), streamContext, emitEvent);
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
return wrapped;
|
|
812
|
+
}
|
|
813
|
+
function mergeModelRunContext(context, override) {
|
|
814
|
+
return { ...context, ...(override ?? {}) };
|
|
815
|
+
}
|
|
816
|
+
function modelStreamRunContext(context, override, alias) {
|
|
817
|
+
const merged = mergeModelRunContext(context, override);
|
|
818
|
+
return {
|
|
819
|
+
...merged,
|
|
820
|
+
modelAlias: alias,
|
|
821
|
+
...(merged.emitRunEvents === true ? { streamId: `model_${ulid()}` } : {})
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
async function* emitTextStreamRunEvents(stream, context, emitEvent) {
|
|
825
|
+
for await (const chunk of stream) {
|
|
826
|
+
if (context.emitRunEvents === true && isTextDeltaChunk(chunk)) {
|
|
827
|
+
await emitEvent({
|
|
828
|
+
type: 'model.delta',
|
|
829
|
+
runId: context.runId,
|
|
830
|
+
...(context.agentId ? { agentId: context.agentId } : {}),
|
|
831
|
+
...(context.workflowId ? { workflowId: context.workflowId } : {}),
|
|
832
|
+
...(context.modelAlias ? { modelAlias: context.modelAlias } : {}),
|
|
833
|
+
streamId: context.streamId,
|
|
834
|
+
delta: chunk.text
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
yield chunk;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
async function* emitObjectStreamRunEvents(stream, context, emitEvent) {
|
|
841
|
+
for await (const chunk of stream) {
|
|
842
|
+
if (context.emitRunEvents === true && isObjectPartialChunk(chunk)) {
|
|
843
|
+
await emitEvent({
|
|
844
|
+
type: 'model.object.partial',
|
|
845
|
+
runId: context.runId,
|
|
846
|
+
...(context.agentId ? { agentId: context.agentId } : {}),
|
|
847
|
+
...(context.workflowId ? { workflowId: context.workflowId } : {}),
|
|
848
|
+
...(context.modelAlias ? { modelAlias: context.modelAlias } : {}),
|
|
849
|
+
streamId: context.streamId,
|
|
850
|
+
partial: chunk.partial
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
else if (context.emitRunEvents === true && isObjectFinishChunk(chunk)) {
|
|
854
|
+
await emitEvent({
|
|
855
|
+
type: 'model.object',
|
|
856
|
+
runId: context.runId,
|
|
857
|
+
...(context.agentId ? { agentId: context.agentId } : {}),
|
|
858
|
+
...(context.workflowId ? { workflowId: context.workflowId } : {}),
|
|
859
|
+
...(context.modelAlias ? { modelAlias: context.modelAlias } : {}),
|
|
860
|
+
...(context.streamId ? { streamId: context.streamId } : {}),
|
|
861
|
+
object: chunk.object,
|
|
862
|
+
...(chunk.usage ? { usage: chunk.usage } : {})
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
yield chunk;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function isTextDeltaChunk(chunk) {
|
|
869
|
+
return Boolean(chunk && typeof chunk === 'object' && chunk.kind === 'delta' && typeof chunk.text === 'string');
|
|
870
|
+
}
|
|
871
|
+
function isObjectPartialChunk(chunk) {
|
|
872
|
+
return Boolean(chunk && typeof chunk === 'object' && chunk.kind === 'partial');
|
|
873
|
+
}
|
|
874
|
+
function isObjectFinishChunk(chunk) {
|
|
875
|
+
return Boolean(chunk && typeof chunk === 'object' && chunk.kind === 'finish' && Object.prototype.hasOwnProperty.call(chunk, 'object'));
|
|
876
|
+
}
|
|
767
877
|
function configureHarnessAdapters(context, models, state, sandbox, memory, tools) {
|
|
768
878
|
const seen = new Set();
|
|
769
879
|
for (const alias of Object.values(models)) {
|
|
@@ -940,12 +1050,12 @@ function sanitizeEventForPersistence(event) {
|
|
|
940
1050
|
case 'model.message':
|
|
941
1051
|
return { agentId: event.agentId, message: '[redacted]' };
|
|
942
1052
|
case 'model.delta':
|
|
943
|
-
return {
|
|
1053
|
+
return { ...modelStreamEventMeta(event), delta: '[redacted]' };
|
|
944
1054
|
case 'model.object.partial':
|
|
945
|
-
return { ...(event
|
|
1055
|
+
return { ...modelStreamEventMeta(event), partial: '[redacted]' };
|
|
946
1056
|
case 'model.object':
|
|
947
1057
|
return {
|
|
948
|
-
...(event
|
|
1058
|
+
...modelStreamEventMeta(event),
|
|
949
1059
|
object: '[redacted]',
|
|
950
1060
|
...(event.usage ? { usage: event.usage } : {})
|
|
951
1061
|
};
|
|
@@ -973,6 +1083,14 @@ function sanitizeEventForPersistence(event) {
|
|
|
973
1083
|
}
|
|
974
1084
|
}
|
|
975
1085
|
}
|
|
1086
|
+
function modelStreamEventMeta(event) {
|
|
1087
|
+
return {
|
|
1088
|
+
...(event.agentId ? { agentId: event.agentId } : {}),
|
|
1089
|
+
...(event.workflowId ? { workflowId: event.workflowId } : {}),
|
|
1090
|
+
...(event.modelAlias ? { modelAlias: event.modelAlias } : {}),
|
|
1091
|
+
...(event.streamId ? { streamId: event.streamId } : {})
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
976
1094
|
function isJsonRecord(value) {
|
|
977
1095
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
978
1096
|
}
|
package/package.json
CHANGED