@nanobpm/urban 0.92.1 → 0.93.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/dist/runtime/core/host.d.ts +124 -1
- package/dist/runtime/core/host.js +3 -0
- package/dist/runtime/engine/nanosdk.d.ts +28 -1
- package/dist/runtime/engine/nanosdk.js +262 -0
- package/dist/runtime/index.d.ts +2 -1
- package/package.json +2 -1
- package/src/runtime/core/host.ts +146 -0
- package/src/runtime/core/modules/actions.test.ts +3 -0
- package/src/runtime/core/modules/agent.test.ts +3 -0
- package/src/runtime/core/modules/api.test.ts +3 -0
- package/src/runtime/core/modules/cancel.test.ts +3 -0
- package/src/runtime/core/modules/connectors.test.ts +3 -0
- package/src/runtime/core/modules/datasource.migrations.test.ts +3 -0
- package/src/runtime/core/modules/instance-tracking.test.ts +3 -0
- package/src/runtime/core/modules/mcp.test.ts +6 -0
- package/src/runtime/core/modules/pages.test.ts +3 -0
- package/src/runtime/core/modules/surfaces.test.ts +6 -0
- package/src/runtime/core/modules/triggers.test.ts +3 -0
- package/src/runtime/core/modules/workers.test.ts +3 -0
- package/src/runtime/core/runtime.extensions.test.ts +9 -0
- package/src/runtime/core/runtime.test.ts +9 -0
- package/src/runtime/engine/nanosdk.test.ts +209 -0
- package/src/runtime/engine/nanosdk.ts +305 -0
- package/src/runtime/index.ts +16 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TranscriptContentBlock, TranscriptToolCall, TranscriptTurnMetrics, TranscriptTurnRole } from "@nanobpm/agentic/transcript";
|
|
1
2
|
/** A minimal HTTP request as seen by a mounted surface/trigger handler. */
|
|
2
3
|
export interface HttpRequest {
|
|
3
4
|
method: string;
|
|
@@ -537,6 +538,95 @@ export interface JobSummary {
|
|
|
537
538
|
readonly elementId?: string;
|
|
538
539
|
readonly deadline?: string;
|
|
539
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* Aggregated metrics for an agent instance across all of its model calls — the
|
|
543
|
+
* `AgentInstanceMetrics` the engine rolls up on the instance itself (distinct from the
|
|
544
|
+
* per-turn {@link TranscriptTurnMetrics} carried on an {@link AgentHistoryRecord}). Total
|
|
545
|
+
* `inputTokens`/`outputTokens` consumed, and the count of `modelCalls`/`toolCalls` made.
|
|
546
|
+
*/
|
|
547
|
+
export interface AgentInstanceMetrics {
|
|
548
|
+
readonly inputTokens: number;
|
|
549
|
+
readonly outputTokens: number;
|
|
550
|
+
readonly modelCalls: number;
|
|
551
|
+
readonly toolCalls: number;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* A single engine-native agent instance as {@link EngineClient.searchAgentInstances} /
|
|
555
|
+
* {@link EngineClient.getAgentInstance} report it — the projection of the engine's
|
|
556
|
+
* `AgentInstanceResult`. Carries the `agentInstanceKey` a caller passes to
|
|
557
|
+
* {@link EngineClient.searchAgentInstanceHistory}, the lifecycle `status` (a bare string —
|
|
558
|
+
* the engine's `AgentInstanceStatusEnum` is broad: `INITIALIZING`/`THINKING`/`TOOL_CALLING`/
|
|
559
|
+
* `IDLE`/`COMPLETED`/…), the owning `processInstanceKey`, and best-effort diagnostics
|
|
560
|
+
* (`elementId`, the owning `elementInstanceKeys`, the process-definition linkage, aggregated
|
|
561
|
+
* `metrics`, and the creation/update/completion timestamps). Optional fields are omitted when
|
|
562
|
+
* the engine does not report them.
|
|
563
|
+
*/
|
|
564
|
+
export interface AgentInstanceSummary {
|
|
565
|
+
readonly agentInstanceKey: string;
|
|
566
|
+
readonly status: string;
|
|
567
|
+
readonly processInstanceKey: string;
|
|
568
|
+
readonly elementId?: string;
|
|
569
|
+
readonly elementInstanceKeys?: readonly string[];
|
|
570
|
+
readonly rootProcessInstanceKey?: string;
|
|
571
|
+
readonly processDefinitionKey?: string;
|
|
572
|
+
readonly processDefinitionId?: string;
|
|
573
|
+
readonly metrics?: AgentInstanceMetrics;
|
|
574
|
+
readonly creationDate?: string;
|
|
575
|
+
readonly lastUpdatedDate?: string;
|
|
576
|
+
readonly completionDate?: string;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* A single agent-instance history item as {@link EngineClient.searchAgentInstanceHistory}
|
|
580
|
+
* reports it — the projection of the engine's `AgentInstanceHistoryItemResult`, i.e. one
|
|
581
|
+
* Camunda `AgentHistoryRecordValue`. Its conversation grammar (`role`, `content` blocks,
|
|
582
|
+
* `toolCalls`, per-turn `metrics`) reuses the `@nanobpm/agentic/transcript` parity types the
|
|
583
|
+
* transcript store already models, so the engine-read seam and the transcript store project
|
|
584
|
+
* the same shape (No Drift Surfaces). `historyItemKey` is the stable, creation-ordered
|
|
585
|
+
* identity; `loopIteration` is the agent-loop counter (one LLM call + its tool dispatches +
|
|
586
|
+
* their results share an iteration); `commitStatus` is the engine's `COMMITTED`/`PENDING`/
|
|
587
|
+
* `DISCARDED` flag (a bare string). `metrics` is present only when the engine recorded per-call
|
|
588
|
+
* metrics for the item; `elementInstanceKey`/`jobKey`/`producedAt` are best-effort diagnostics,
|
|
589
|
+
* omitted when unreported.
|
|
590
|
+
*/
|
|
591
|
+
export interface AgentHistoryRecord {
|
|
592
|
+
readonly historyItemKey: string;
|
|
593
|
+
readonly agentInstanceKey: string;
|
|
594
|
+
readonly loopIteration: number;
|
|
595
|
+
readonly role: TranscriptTurnRole;
|
|
596
|
+
readonly content: readonly TranscriptContentBlock[];
|
|
597
|
+
readonly toolCalls: readonly TranscriptToolCall[];
|
|
598
|
+
readonly metrics?: TranscriptTurnMetrics;
|
|
599
|
+
readonly commitStatus: string;
|
|
600
|
+
readonly elementInstanceKey?: string;
|
|
601
|
+
readonly jobKey?: string;
|
|
602
|
+
readonly producedAt?: string;
|
|
603
|
+
}
|
|
604
|
+
/** Selectors for {@link EngineClient.searchAgentInstances}. Every field is optional; an
|
|
605
|
+
* omitted/blank selector is not applied (No Drift Surfaces — the same presence rule the key
|
|
606
|
+
* selectors elsewhere in this seam use). */
|
|
607
|
+
export interface AgentInstanceFilter {
|
|
608
|
+
/** Only agent instances owned by this process instance. */
|
|
609
|
+
readonly processInstanceKey?: string;
|
|
610
|
+
/** Only agent instances belonging to this *root* process-instance hierarchy. */
|
|
611
|
+
readonly rootProcessInstanceKey?: string;
|
|
612
|
+
/** Only agent instances in this lifecycle status (the engine's `AgentInstanceStatusEnum`). */
|
|
613
|
+
readonly status?: string;
|
|
614
|
+
/** Only agent instances owned by this BPMN element (the AI-agent task / ad-hoc sub-process). */
|
|
615
|
+
readonly elementId?: string;
|
|
616
|
+
}
|
|
617
|
+
/** Selectors for {@link EngineClient.searchAgentInstanceHistory} (the `agentInstanceKey` is a
|
|
618
|
+
* required positional argument, not a filter field). Every field is optional. Note the engine
|
|
619
|
+
* defaults an unfiltered history search to `COMMITTED` items only. */
|
|
620
|
+
export interface AgentHistoryFilter {
|
|
621
|
+
/** Only history items with this conversation role. Note that `TranscriptTurnRole` is the wider
|
|
622
|
+
* transcript union: only the engine-side subset `"USER" | "ASSISTANT" | "TOOL_RESULT"` is
|
|
623
|
+
* applied as a filter — `"CONFIGURATION"`/`"UNSPECIFIED"` are ignored (not forwarded). */
|
|
624
|
+
readonly role?: TranscriptTurnRole;
|
|
625
|
+
/** Only history items produced in this agent-loop iteration. */
|
|
626
|
+
readonly loopIteration?: number;
|
|
627
|
+
/** Only history items produced under this element instance. */
|
|
628
|
+
readonly elementInstanceKey?: string;
|
|
629
|
+
}
|
|
540
630
|
/**
|
|
541
631
|
* EngineClient is the seam onto a Nano engine. The SDK/REST-backed adapter implements
|
|
542
632
|
* it against a live engine; tests implement it in-memory. Core modules depend only on this.
|
|
@@ -712,6 +802,39 @@ export interface EngineClient {
|
|
|
712
802
|
* caller distinguishes "have it" from "don't" without a try/catch. A zero-wait read.
|
|
713
803
|
*/
|
|
714
804
|
getProcessDefinitionXml(processDefinitionKey: string): Promise<string | null>;
|
|
805
|
+
/**
|
|
806
|
+
* Search engine-native agent instances (`POST /agent-instances/search`), optionally narrowed
|
|
807
|
+
* by owning `processInstanceKey`/`rootProcessInstanceKey`, lifecycle `status`, or `elementId`.
|
|
808
|
+
* The read entry point onto the engine's AgentInstance/AgentHistory model: an
|
|
809
|
+
* {@link AgentInstanceSummary} carries the `agentInstanceKey` a caller passes to
|
|
810
|
+
* {@link searchAgentInstanceHistory} to read that instance's conversation. An eventually
|
|
811
|
+
* consistent (zero-wait) read; each malformed row (no `agentInstanceKey`/`processInstanceKey`)
|
|
812
|
+
* is dropped rather than surfaced.
|
|
813
|
+
*/
|
|
814
|
+
searchAgentInstances(filter?: AgentInstanceFilter): Promise<AgentInstanceSummary[]>;
|
|
815
|
+
/**
|
|
816
|
+
* Search one agent instance's conversation history
|
|
817
|
+
* (`POST /agent-instances/{agentInstanceKey}/history/search`) — the ordered
|
|
818
|
+
* {@link AgentHistoryRecord}s (turns) the engine retains for `agentInstanceKey`, optionally
|
|
819
|
+
* narrowed by `role`/`loopIteration`/`elementInstanceKey`. This is the production read path
|
|
820
|
+
* onto engine AgentHistory the historical-transcript + metrics consumer builds on
|
|
821
|
+
* (nanobpm/nano-workforce#747). Each record's conversation grammar reuses the
|
|
822
|
+
* `@nanobpm/agentic/transcript` parity shapes (roles, content blocks, per-turn metrics, tool
|
|
823
|
+
* calls). Note the engine defaults an unfiltered search to `COMMITTED` items only. A blank
|
|
824
|
+
* `agentInstanceKey` addresses no instance, so it returns the empty list rather than issuing a
|
|
825
|
+
* request with an empty path segment. An eventually consistent (zero-wait) read; malformed rows
|
|
826
|
+
* are dropped.
|
|
827
|
+
*/
|
|
828
|
+
searchAgentInstanceHistory(agentInstanceKey: string, filter?: AgentHistoryFilter): Promise<AgentHistoryRecord[]>;
|
|
829
|
+
/**
|
|
830
|
+
* Fetch a single agent instance by its key (`GET /agent-instances/{agentInstanceKey}`), or
|
|
831
|
+
* `null` when it cannot be resolved — the key is blank, no such agent instance exists (a 404),
|
|
832
|
+
* or the fetch otherwise fails. A read is treated as absence rather than propagating (mirroring
|
|
833
|
+
* {@link getForm}/{@link getElementInstance}/{@link getProcessDefinitionXml}), so a caller
|
|
834
|
+
* distinguishes "have it" from "don't" without a try/catch. A zero-wait read returning the same
|
|
835
|
+
* {@link AgentInstanceSummary} shape as {@link searchAgentInstances}.
|
|
836
|
+
*/
|
|
837
|
+
getAgentInstance(agentInstanceKey: string): Promise<AgentInstanceSummary | null>;
|
|
715
838
|
/**
|
|
716
839
|
* Resolve an open incident by key (`POST /v2/incidents/{incidentKey}/resolution`), unblocking
|
|
717
840
|
* the parked token: a job incident returns the job to the activatable pool (so it must have
|
|
@@ -766,4 +889,4 @@ export interface EngineClient {
|
|
|
766
889
|
* and `getForm` before it) that a purely structural `implements EngineClient` check no
|
|
767
890
|
* longer catches once the fake is published/compiled against an older `@nanobpm/urban`.
|
|
768
891
|
*/
|
|
769
|
-
export declare const ENGINE_CLIENT_METHODS: readonly ["deployResources", "createInstance", "cancelInstance", "publishMessage", "searchUserTasks", "openUserTasks", "getForm", "completeUserTask", "searchProcessInstances", "searchElementInstances", "searchElementInstanceWaitStates", "getElementInstance", "searchIncidents", "searchVariables", "searchJobs", "getProcessDefinitionXml", "resolveIncident", "updateJobRetries", "setVariables", "registerWorker", "close"];
|
|
892
|
+
export declare const ENGINE_CLIENT_METHODS: readonly ["deployResources", "createInstance", "cancelInstance", "publishMessage", "searchUserTasks", "openUserTasks", "getForm", "completeUserTask", "searchProcessInstances", "searchElementInstances", "searchElementInstanceWaitStates", "getElementInstance", "searchIncidents", "searchVariables", "searchJobs", "getProcessDefinitionXml", "searchAgentInstances", "searchAgentInstanceHistory", "getAgentInstance", "resolveIncident", "updateJobRetries", "setVariables", "registerWorker", "close"];
|
|
@@ -123,6 +123,9 @@ export const ENGINE_CLIENT_METHODS = [
|
|
|
123
123
|
"searchVariables",
|
|
124
124
|
"searchJobs",
|
|
125
125
|
"getProcessDefinitionXml",
|
|
126
|
+
"searchAgentInstances",
|
|
127
|
+
"searchAgentInstanceHistory",
|
|
128
|
+
"getAgentInstance",
|
|
126
129
|
"resolveIncident",
|
|
127
130
|
"updateJobRetries",
|
|
128
131
|
"setVariables",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EngineClient, ElementInstanceState, ElementInstanceSummary, ElementInstanceFilter, ElementInstanceWaitState, ElementInstanceWaitStateFilter, WaitStateType, FormSchema, IncidentFilter, IncidentState, IncidentSummary, JobFilter, JobHandler, JobSummary, ProcessInstanceSnapshot, ProcessInstanceState, UserTaskState, UserTaskSummary, UserTaskFilter, VariableFilter, VariableSummary, WorkerSubscription } from "../core/host.ts";
|
|
1
|
+
import type { EngineClient, AgentHistoryFilter, AgentHistoryRecord, AgentInstanceFilter, AgentInstanceSummary, ElementInstanceState, ElementInstanceSummary, ElementInstanceFilter, ElementInstanceWaitState, ElementInstanceWaitStateFilter, WaitStateType, FormSchema, IncidentFilter, IncidentState, IncidentSummary, JobFilter, JobHandler, JobSummary, ProcessInstanceSnapshot, ProcessInstanceState, UserTaskState, UserTaskSummary, UserTaskFilter, VariableFilter, VariableSummary, WorkerSubscription } from "../core/host.ts";
|
|
2
2
|
import type { EngineSdkClient } from "./sdk.ts";
|
|
3
3
|
/** Coerce an engine response's process-instance key to a non-empty string, or
|
|
4
4
|
* throw — a missing key means a malformed/partial response, not a real instance. */
|
|
@@ -55,6 +55,18 @@ export declare function mapVariableRow(row: Record<string, unknown>, log: Log):
|
|
|
55
55
|
* `state` is passed through as a bare string (the engine's job-state enum is broad). The single
|
|
56
56
|
* source of truth `searchJobs` extracts through. */
|
|
57
57
|
export declare function mapJobRow(row: Record<string, unknown>, log: Log): JobSummary | undefined;
|
|
58
|
+
/** Map one engine `AgentInstanceResult` row onto an {@link AgentInstanceSummary}, or `undefined`
|
|
59
|
+
* when the row is malformed (missing the `agentInstanceKey` or owning `processInstanceKey`). The
|
|
60
|
+
* single source of truth `searchAgentInstances`/`getAgentInstance` extract through, so the two
|
|
61
|
+
* cannot drift. Optional linkage/metrics/timestamp fields are emitted only when the engine
|
|
62
|
+
* reports them. */
|
|
63
|
+
export declare function mapAgentInstanceRow(row: Record<string, unknown>, log: Log): AgentInstanceSummary | undefined;
|
|
64
|
+
/** Map one engine `AgentInstanceHistoryItemResult` row onto an {@link AgentHistoryRecord}, or
|
|
65
|
+
* `undefined` when the row is malformed (missing the `historyItemKey` or owning
|
|
66
|
+
* `agentInstanceKey`). `content`/`toolCalls` drop any individually-malformed block; `loopIteration`
|
|
67
|
+
* defaults to `0` when unreported; `commitStatus` passes through as a bare string. The single
|
|
68
|
+
* source of truth `searchAgentInstanceHistory` extracts through. */
|
|
69
|
+
export declare function mapAgentHistoryRow(row: Record<string, unknown>, log: Log): AgentHistoryRecord | undefined;
|
|
58
70
|
/** A job as delivered to a nano-sdk job handler: the frame fields plus the
|
|
59
71
|
* acknowledgement actions the handler must call. */
|
|
60
72
|
export interface NanoSdkActivatedJob {
|
|
@@ -161,6 +173,18 @@ export interface NanoSdkClient {
|
|
|
161
173
|
getProcessDefinitionXml(input: {
|
|
162
174
|
processDefinitionKey: string;
|
|
163
175
|
}, consistency?: unknown, options?: unknown): Promise<unknown>;
|
|
176
|
+
searchAgentInstances(input: {
|
|
177
|
+
filter?: Record<string, unknown>;
|
|
178
|
+
page?: Record<string, unknown>;
|
|
179
|
+
}, consistency?: unknown, options?: unknown): Promise<Record<string, unknown>>;
|
|
180
|
+
searchAgentInstanceHistory(input: {
|
|
181
|
+
agentInstanceKey: string;
|
|
182
|
+
filter?: Record<string, unknown>;
|
|
183
|
+
page?: Record<string, unknown>;
|
|
184
|
+
}, consistency?: unknown, options?: unknown): Promise<Record<string, unknown>>;
|
|
185
|
+
getAgentInstance(input: {
|
|
186
|
+
agentInstanceKey: string;
|
|
187
|
+
}, consistency?: unknown, options?: unknown): Promise<Record<string, unknown>>;
|
|
164
188
|
resolveIncident(input: {
|
|
165
189
|
incidentKey: string;
|
|
166
190
|
}, options?: unknown): Promise<unknown>;
|
|
@@ -246,6 +270,9 @@ export declare class SdkEngineClient implements EngineClient {
|
|
|
246
270
|
searchVariables(filter?: VariableFilter): Promise<VariableSummary[]>;
|
|
247
271
|
searchJobs(filter?: JobFilter): Promise<JobSummary[]>;
|
|
248
272
|
getProcessDefinitionXml(processDefinitionKey: string): Promise<string | null>;
|
|
273
|
+
searchAgentInstances(filter?: AgentInstanceFilter): Promise<AgentInstanceSummary[]>;
|
|
274
|
+
searchAgentInstanceHistory(agentInstanceKey: string, filter?: AgentHistoryFilter): Promise<AgentHistoryRecord[]>;
|
|
275
|
+
getAgentInstance(agentInstanceKey: string): Promise<AgentInstanceSummary | null>;
|
|
249
276
|
resolveIncident(input: {
|
|
250
277
|
incidentKey: string;
|
|
251
278
|
}): Promise<void>;
|
|
@@ -405,6 +405,191 @@ export function mapJobRow(row, log) {
|
|
|
405
405
|
...(deadline ? { deadline } : {}),
|
|
406
406
|
};
|
|
407
407
|
}
|
|
408
|
+
/** The agent-instance conversation roles the engine's `AgentInstanceHistoryRoleEnum` reports —
|
|
409
|
+
* a strict subset of the transcript {@link TranscriptTurnRole} union it maps onto, so the two
|
|
410
|
+
* cannot drift. A row whose role is anything else is normalized to `"UNSPECIFIED"`. */
|
|
411
|
+
const AGENT_HISTORY_ROLES = ["USER", "ASSISTANT", "TOOL_RESULT"];
|
|
412
|
+
/** Map an engine history-item role onto the transcript {@link TranscriptTurnRole} union, or
|
|
413
|
+
* `"UNSPECIFIED"` when the engine reports an unknown/absent role — a lossy-but-safe fallback so a
|
|
414
|
+
* record is surfaced rather than dropped for a role the engine grows later. */
|
|
415
|
+
function normalizeAgentHistoryRole(value) {
|
|
416
|
+
const role = AGENT_HISTORY_ROLES.find((r) => r === value);
|
|
417
|
+
return role ?? "UNSPECIFIED";
|
|
418
|
+
}
|
|
419
|
+
/** The content-block type discriminators the engine's `AgentInstanceMessageContentTypeEnum`
|
|
420
|
+
* reports, mapped onto the transcript {@link TranscriptContentType} union. */
|
|
421
|
+
const AGENT_CONTENT_TYPES = ["TEXT", "DOCUMENT", "OBJECT"];
|
|
422
|
+
/** Map one engine `AgentInstanceMessageContent` row onto a transcript {@link TranscriptContentBlock},
|
|
423
|
+
* or `undefined` when malformed. TEXT carries `text`, DOCUMENT carries `documentReference`, OBJECT
|
|
424
|
+
* carries `object` — exactly the per-`contentType` payload the transcript shape pins, so the
|
|
425
|
+
* engine-read seam and the transcript store project content identically (No Drift Surfaces). An
|
|
426
|
+
* unknown/absent discriminator maps to an `UNSPECIFIED` block carrying no payload. */
|
|
427
|
+
function mapAgentContentBlock(value) {
|
|
428
|
+
if (!isRecord(value))
|
|
429
|
+
return undefined;
|
|
430
|
+
const contentType = AGENT_CONTENT_TYPES.find((t) => t === value.contentType) ?? "UNSPECIFIED";
|
|
431
|
+
if (contentType === "TEXT") {
|
|
432
|
+
return { contentType, text: typeof value.text === "string" ? value.text : "" };
|
|
433
|
+
}
|
|
434
|
+
if (contentType === "DOCUMENT") {
|
|
435
|
+
const documentReference = presentText(value.documentReference);
|
|
436
|
+
return { contentType, documentReference: documentReference ?? "" };
|
|
437
|
+
}
|
|
438
|
+
if (contentType === "OBJECT") {
|
|
439
|
+
return { contentType, object: value.object };
|
|
440
|
+
}
|
|
441
|
+
return { contentType };
|
|
442
|
+
}
|
|
443
|
+
/** Map one engine `AgentInstanceToolCall` row onto a transcript {@link TranscriptToolCall}, or
|
|
444
|
+
* `undefined` when it carries no `toolCallId`/`toolName` (the identity a caller correlates on). A
|
|
445
|
+
* TOOL_RESULT item reports `arguments: null`; normalize that to an empty object so the
|
|
446
|
+
* transcript shape's non-null `arguments` invariant holds. */
|
|
447
|
+
function mapAgentToolCall(value) {
|
|
448
|
+
if (!isRecord(value))
|
|
449
|
+
return undefined;
|
|
450
|
+
const toolCallId = presentText(value.toolCallId);
|
|
451
|
+
const toolName = presentText(value.toolName);
|
|
452
|
+
if (toolCallId === undefined || toolName === undefined)
|
|
453
|
+
return undefined;
|
|
454
|
+
const elementId = presentText(value.elementId);
|
|
455
|
+
const args = isRecord(value.arguments) ? value.arguments : {};
|
|
456
|
+
return {
|
|
457
|
+
toolCallId,
|
|
458
|
+
toolName,
|
|
459
|
+
...(elementId ? { elementId } : {}),
|
|
460
|
+
arguments: args,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
/** Map one engine `AgentInstanceHistoryItemMetrics` onto per-turn {@link TranscriptTurnMetrics},
|
|
464
|
+
* or `undefined` when the engine recorded none (a `null` metrics object). The engine's per-item
|
|
465
|
+
* metrics carry only `inputTokens`/`outputTokens`/`durationMs` (each nullable); the transcript
|
|
466
|
+
* shape additionally models reasoning/cache token counts the engine does not report per item, so
|
|
467
|
+
* those default to `0` (the "none reported" count) — a coercion, not a second projection. */
|
|
468
|
+
function mapAgentHistoryMetrics(value) {
|
|
469
|
+
if (!isRecord(value))
|
|
470
|
+
return undefined;
|
|
471
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
472
|
+
return {
|
|
473
|
+
inputTokens: num(value.inputTokens),
|
|
474
|
+
outputTokens: num(value.outputTokens),
|
|
475
|
+
reasoningTokenCount: 0,
|
|
476
|
+
cacheCreationTokenCount: 0,
|
|
477
|
+
cacheReadTokenCount: 0,
|
|
478
|
+
durationMs: num(value.durationMs),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
/** Map one engine `AgentInstanceMetrics` (the instance-level aggregate) onto
|
|
482
|
+
* {@link AgentInstanceMetrics}, or `undefined` when the row carries none. */
|
|
483
|
+
function mapAgentInstanceMetrics(value) {
|
|
484
|
+
if (!isRecord(value))
|
|
485
|
+
return undefined;
|
|
486
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
487
|
+
return {
|
|
488
|
+
inputTokens: num(value.inputTokens),
|
|
489
|
+
outputTokens: num(value.outputTokens),
|
|
490
|
+
modelCalls: num(value.modelCalls),
|
|
491
|
+
toolCalls: num(value.toolCalls),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/** Map one engine `AgentInstanceResult` row onto an {@link AgentInstanceSummary}, or `undefined`
|
|
495
|
+
* when the row is malformed (missing the `agentInstanceKey` or owning `processInstanceKey`). The
|
|
496
|
+
* single source of truth `searchAgentInstances`/`getAgentInstance` extract through, so the two
|
|
497
|
+
* cannot drift. Optional linkage/metrics/timestamp fields are emitted only when the engine
|
|
498
|
+
* reports them. */
|
|
499
|
+
export function mapAgentInstanceRow(row, log) {
|
|
500
|
+
const agentInstanceKey = presentEngineKey(row.agentInstanceKey);
|
|
501
|
+
if (agentInstanceKey === undefined) {
|
|
502
|
+
log("warn", "skipping agent instance with no agentInstanceKey in engine response");
|
|
503
|
+
return undefined;
|
|
504
|
+
}
|
|
505
|
+
const processInstanceKey = presentEngineKey(row.processInstanceKey);
|
|
506
|
+
if (processInstanceKey === undefined) {
|
|
507
|
+
log("warn", "skipping agent instance with no processInstanceKey in engine response", {
|
|
508
|
+
agentInstanceKey,
|
|
509
|
+
});
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
const status = presentText(row.status) ?? "UNKNOWN";
|
|
513
|
+
const elementId = presentText(row.elementId);
|
|
514
|
+
const rootProcessInstanceKey = presentEngineKey(row.rootProcessInstanceKey);
|
|
515
|
+
const processDefinitionKey = presentEngineKey(row.processDefinitionKey);
|
|
516
|
+
const processDefinitionId = presentText(row.processDefinitionId);
|
|
517
|
+
const elementInstanceKeys = Array.isArray(row.elementInstanceKeys)
|
|
518
|
+
? row.elementInstanceKeys
|
|
519
|
+
.map((k) => presentEngineKey(k))
|
|
520
|
+
.filter((k) => k !== undefined)
|
|
521
|
+
: undefined;
|
|
522
|
+
const metrics = mapAgentInstanceMetrics(row.metrics);
|
|
523
|
+
const creationDate = presentText(row.creationDate);
|
|
524
|
+
const lastUpdatedDate = presentText(row.lastUpdatedDate);
|
|
525
|
+
const completionDate = presentText(row.completionDate);
|
|
526
|
+
return {
|
|
527
|
+
agentInstanceKey,
|
|
528
|
+
status,
|
|
529
|
+
processInstanceKey,
|
|
530
|
+
...(elementId ? { elementId } : {}),
|
|
531
|
+
...(elementInstanceKeys && elementInstanceKeys.length > 0 ? { elementInstanceKeys } : {}),
|
|
532
|
+
...(rootProcessInstanceKey ? { rootProcessInstanceKey } : {}),
|
|
533
|
+
...(processDefinitionKey ? { processDefinitionKey } : {}),
|
|
534
|
+
...(processDefinitionId ? { processDefinitionId } : {}),
|
|
535
|
+
...(metrics ? { metrics } : {}),
|
|
536
|
+
...(creationDate ? { creationDate } : {}),
|
|
537
|
+
...(lastUpdatedDate ? { lastUpdatedDate } : {}),
|
|
538
|
+
...(completionDate ? { completionDate } : {}),
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
/** Map one engine `AgentInstanceHistoryItemResult` row onto an {@link AgentHistoryRecord}, or
|
|
542
|
+
* `undefined` when the row is malformed (missing the `historyItemKey` or owning
|
|
543
|
+
* `agentInstanceKey`). `content`/`toolCalls` drop any individually-malformed block; `loopIteration`
|
|
544
|
+
* defaults to `0` when unreported; `commitStatus` passes through as a bare string. The single
|
|
545
|
+
* source of truth `searchAgentInstanceHistory` extracts through. */
|
|
546
|
+
export function mapAgentHistoryRow(row, log) {
|
|
547
|
+
const historyItemKey = presentEngineKey(row.historyItemKey);
|
|
548
|
+
if (historyItemKey === undefined) {
|
|
549
|
+
log("warn", "skipping agent history item with no historyItemKey in engine response");
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
const agentInstanceKey = presentEngineKey(row.agentInstanceKey);
|
|
553
|
+
if (agentInstanceKey === undefined) {
|
|
554
|
+
log("warn", "skipping agent history item with no agentInstanceKey in engine response", {
|
|
555
|
+
historyItemKey,
|
|
556
|
+
});
|
|
557
|
+
return undefined;
|
|
558
|
+
}
|
|
559
|
+
const loopIteration = typeof row.loopIteration === "number" && Number.isFinite(row.loopIteration)
|
|
560
|
+
? row.loopIteration
|
|
561
|
+
: 0;
|
|
562
|
+
const content = Array.isArray(row.content)
|
|
563
|
+
? row.content.flatMap((c) => {
|
|
564
|
+
const block = mapAgentContentBlock(c);
|
|
565
|
+
return block ? [block] : [];
|
|
566
|
+
})
|
|
567
|
+
: [];
|
|
568
|
+
const toolCalls = Array.isArray(row.toolCalls)
|
|
569
|
+
? row.toolCalls.flatMap((t) => {
|
|
570
|
+
const call = mapAgentToolCall(t);
|
|
571
|
+
return call ? [call] : [];
|
|
572
|
+
})
|
|
573
|
+
: [];
|
|
574
|
+
const metrics = mapAgentHistoryMetrics(row.metrics);
|
|
575
|
+
const commitStatus = presentText(row.commitStatus) ?? "COMMITTED";
|
|
576
|
+
const elementInstanceKey = presentEngineKey(row.elementInstanceKey);
|
|
577
|
+
const jobKey = presentEngineKey(row.jobKey);
|
|
578
|
+
const producedAt = presentText(row.producedAt);
|
|
579
|
+
return {
|
|
580
|
+
historyItemKey,
|
|
581
|
+
agentInstanceKey,
|
|
582
|
+
loopIteration,
|
|
583
|
+
role: normalizeAgentHistoryRole(row.role),
|
|
584
|
+
content,
|
|
585
|
+
toolCalls,
|
|
586
|
+
...(metrics ? { metrics } : {}),
|
|
587
|
+
commitStatus,
|
|
588
|
+
...(elementInstanceKey ? { elementInstanceKey } : {}),
|
|
589
|
+
...(jobKey ? { jobKey } : {}),
|
|
590
|
+
...(producedAt ? { producedAt } : {}),
|
|
591
|
+
};
|
|
592
|
+
}
|
|
408
593
|
/**
|
|
409
594
|
* An `EngineClient` backed entirely by a `@nanobpm/nano-sdk` client. Deploy, create,
|
|
410
595
|
* message, and user-task calls map one-to-one onto the SDK's client methods; workers
|
|
@@ -780,6 +965,83 @@ export class SdkEngineClient {
|
|
|
780
965
|
// as "no XML" (null) so a caller distinguishes "have it" from "don't".
|
|
781
966
|
return typeof xml === "string" && xml.trim() !== "" ? xml : null;
|
|
782
967
|
}
|
|
968
|
+
async searchAgentInstances(filter) {
|
|
969
|
+
// Same shape as `searchIncidents`: build the engine filter from the transport-agnostic
|
|
970
|
+
// selectors, read at zero-wait consistency (an agent-instance search is eventually
|
|
971
|
+
// consistent), and map each row through the single `mapAgentInstanceRow` gate, dropping
|
|
972
|
+
// malformed rows. Normalize the request key selectors the same way the response mapper
|
|
973
|
+
// normalizes keys so a blank/padded selector cannot reach the engine as a garbage filter.
|
|
974
|
+
const f = {};
|
|
975
|
+
const processInstanceKey = presentEngineKey(filter?.processInstanceKey);
|
|
976
|
+
if (processInstanceKey)
|
|
977
|
+
f.processInstanceKey = processInstanceKey;
|
|
978
|
+
const rootProcessInstanceKey = presentEngineKey(filter?.rootProcessInstanceKey);
|
|
979
|
+
if (rootProcessInstanceKey)
|
|
980
|
+
f.rootProcessInstanceKey = rootProcessInstanceKey;
|
|
981
|
+
const status = filter?.status?.trim();
|
|
982
|
+
if (status)
|
|
983
|
+
f.status = status;
|
|
984
|
+
const elementId = filter?.elementId?.trim();
|
|
985
|
+
if (elementId)
|
|
986
|
+
f.elementId = elementId;
|
|
987
|
+
const body = await this.client.searchAgentInstances({ filter: f }, { consistency: { waitUpToMs: 0 } });
|
|
988
|
+
const items = Array.isArray(body.items) ? body.items.filter(isRecord) : [];
|
|
989
|
+
return items.flatMap((it) => {
|
|
990
|
+
const mapped = mapAgentInstanceRow(it, this.log);
|
|
991
|
+
return mapped ? [mapped] : [];
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
async searchAgentInstanceHistory(agentInstanceKey, filter) {
|
|
995
|
+
// A blank key can never address an agent instance — short-circuit to the empty list rather
|
|
996
|
+
// than issue a `POST /agent-instances//history/search` with an empty path segment. Normalize a
|
|
997
|
+
// padded-but-valid key so `" 5 "` addresses the same instance, mirroring `getElementInstance`.
|
|
998
|
+
const key = presentEngineKey(agentInstanceKey);
|
|
999
|
+
if (key === undefined)
|
|
1000
|
+
return [];
|
|
1001
|
+
const f = {};
|
|
1002
|
+
// `AgentHistoryFilter.role` is typed as the wider transcript `TranscriptTurnRole` union, which
|
|
1003
|
+
// also carries `CONFIGURATION`/`UNSPECIFIED` — roles the engine's history filter does not
|
|
1004
|
+
// accept and would 4xx on. Gate the selector through the same `AGENT_HISTORY_ROLES` subset the
|
|
1005
|
+
// response mapper uses (No Drift Surfaces) so an unsupported role is dropped, not forwarded.
|
|
1006
|
+
if (filter?.role && AGENT_HISTORY_ROLES.includes(filter.role))
|
|
1007
|
+
f.role = filter.role;
|
|
1008
|
+
if (typeof filter?.loopIteration === "number" && Number.isFinite(filter.loopIteration)) {
|
|
1009
|
+
f.loopIteration = filter.loopIteration;
|
|
1010
|
+
}
|
|
1011
|
+
const elementInstanceKey = presentEngineKey(filter?.elementInstanceKey);
|
|
1012
|
+
if (elementInstanceKey)
|
|
1013
|
+
f.elementInstanceKey = elementInstanceKey;
|
|
1014
|
+
const body = await this.client.searchAgentInstanceHistory({ agentInstanceKey: key, filter: f }, { consistency: { waitUpToMs: 0 } });
|
|
1015
|
+
const items = Array.isArray(body.items) ? body.items.filter(isRecord) : [];
|
|
1016
|
+
return items.flatMap((it) => {
|
|
1017
|
+
const mapped = mapAgentHistoryRow(it, this.log);
|
|
1018
|
+
return mapped ? [mapped] : [];
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
async getAgentInstance(agentInstanceKey) {
|
|
1022
|
+
// A blank key can never address an agent instance — short-circuit to null rather than issue a
|
|
1023
|
+
// `GET /agent-instances/` with an empty segment. Normalize a padded-but-valid key, mirroring
|
|
1024
|
+
// `getElementInstance`/`getProcessDefinitionXml`.
|
|
1025
|
+
const key = presentEngineKey(agentInstanceKey);
|
|
1026
|
+
if (key === undefined)
|
|
1027
|
+
return null;
|
|
1028
|
+
let body;
|
|
1029
|
+
try {
|
|
1030
|
+
body = await this.client.getAgentInstance({ agentInstanceKey: key }, { consistency: { waitUpToMs: 0 } });
|
|
1031
|
+
}
|
|
1032
|
+
catch (err) {
|
|
1033
|
+
// A 404 (no such agent instance) is an expected "not found", not a fault — mirror `getForm`/
|
|
1034
|
+
// `getElementInstance`, which treat a failed fetch as absence (null) rather than propagating.
|
|
1035
|
+
this.log("warn", "getAgentInstance: engine fetch failed", {
|
|
1036
|
+
agentInstanceKey: key,
|
|
1037
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1038
|
+
});
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
if (!isRecord(body))
|
|
1042
|
+
return null;
|
|
1043
|
+
return mapAgentInstanceRow(body, this.log) ?? null;
|
|
1044
|
+
}
|
|
783
1045
|
async resolveIncident(input) {
|
|
784
1046
|
await this.client.resolveIncident({
|
|
785
1047
|
incidentKey: requireEngineKey(input.incidentKey, "incidentKey"),
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export { bindModeToHost, expandEnv, expandEnvString, isBindMode, loadManifest, p
|
|
|
8
8
|
export type { ActionDecl, AppManifest, BindMode, ChatSurface, DataSource, DomainField, DomainType, InstanceTracking, LlmBinding, NetworkConfig, PagesSurface, Surfaces, TaskInboxSurface, Trigger, Worker, } from "./core/manifest.ts";
|
|
9
9
|
export { collectManifestIssues, ManifestValidationError, validateManifest, } from "./core/validate.ts";
|
|
10
10
|
export type { ValidationIssue } from "./core/validate.ts";
|
|
11
|
-
export type { EngineClient, ElementInstanceState, ElementInstanceSummary, ElementInstanceFilter, ElementInstanceWaitState, ElementInstanceWaitStateFilter, WaitStateType, EngineJob, FormSchema, HostContext, HttpHandler, HttpRequest, HttpResponse, HttpServer, IncidentState, EngineIncidentState, IncidentFilter, IncidentSummary, JobFilter, JobHandler, JobSummary, ProcessInstanceSnapshot, ProcessInstanceState, SqliteDb, UserTaskState, UserTaskSummary, UserTaskFilter, VariableFilter, VariableSummary, WatchHandle, WorkerSubscription, } from "./core/host.ts";
|
|
11
|
+
export type { EngineClient, AgentHistoryFilter, AgentHistoryRecord, AgentInstanceFilter, AgentInstanceMetrics, AgentInstanceSummary, ElementInstanceState, ElementInstanceSummary, ElementInstanceFilter, ElementInstanceWaitState, ElementInstanceWaitStateFilter, WaitStateType, EngineJob, FormSchema, HostContext, HttpHandler, HttpRequest, HttpResponse, HttpServer, IncidentState, EngineIncidentState, IncidentFilter, IncidentSummary, JobFilter, JobHandler, JobSummary, ProcessInstanceSnapshot, ProcessInstanceState, SqliteDb, UserTaskState, UserTaskSummary, UserTaskFilter, VariableFilter, VariableSummary, WatchHandle, WorkerSubscription, } from "./core/host.ts";
|
|
12
|
+
export type { TranscriptContentBlock, TranscriptContentType, TranscriptToolCall, TranscriptTurnMetrics, TranscriptTurnRole, } from "@nanobpm/agentic/transcript";
|
|
12
13
|
export { buildFormSchema, parseFormSchema, pickFormLinkage, presentFormIdentifier, resolveFormIdentifier, } from "./core/form-contract.ts";
|
|
13
14
|
export type { AppApi, Mounted, RuntimeContext } from "./core/context.ts";
|
|
14
15
|
export { createLogger } from "./core/logger.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/urban",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.0",
|
|
4
4
|
"description": "Urban: build and run code-first apps on Nano — runtime, derivation toolkit, and CLI in one. Author durable processes with @nanobpm/workflow (defineFlow), a typed datasource, forms, triggers and surfaces from a nano.app.json manifest, on Node or Deno.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
},
|
|
104
104
|
"dependencies": {
|
|
105
105
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
106
|
+
"@nanobpm/agentic": "^0.13.0",
|
|
106
107
|
"@nanobpm/nano-app-schema": "^0.9.0",
|
|
107
108
|
"@nanobpm/nano-sdk": "^1.2.5",
|
|
108
109
|
"@nanobpm/workflow": "^0.14.0",
|