melchizedek-agents 0.18.0 → 0.20.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/.env.example +12 -0
- package/CHANGELOG.md +1001 -0
- package/DOCUMENTATION.md +252 -65
- package/QUICKSTART.md +9 -6
- package/README.md +42 -4
- package/config/agents/examples/harness.yaml +3 -1
- package/config/agents/examples/model_zoo.yaml +10 -9
- package/config/agents/syndicateSchema.yaml +53 -16
- package/config/agents/templates/account_memory.yaml +2 -6
- package/config/agents/templates/case_desk.yaml +3 -9
- package/config/agents/templates/draft_review.yaml +3 -9
- package/config/agents/templates/intake_extractor.yaml +1 -3
- package/config/agents/templates/research_brief.yaml +3 -9
- package/config/agents/templates/research_desk.yaml +3 -9
- package/config/agents/templates/review_panel.yaml +4 -12
- package/config/agents/templates/support_triage.yaml +4 -12
- package/config/agents/templates/systems_operator.yaml +2 -6
- package/db/hardening.sql +7 -1
- package/db/migrations/0013_tool_credentials.sql +260 -0
- package/dist/lib/a2a/app.d.ts +70 -6
- package/dist/lib/a2a/app.js +107 -2
- package/dist/lib/a2a/executor.d.ts +20 -3
- package/dist/lib/a2a/executor.js +66 -7
- package/dist/lib/a2a/remoteAgent.d.ts +24 -9
- package/dist/lib/a2a/remoteAgent.js +31 -14
- package/dist/lib/adkPeer.d.ts +63 -0
- package/dist/lib/adkPeer.js +240 -0
- package/dist/lib/compile.d.ts +159 -30
- package/dist/lib/compile.js +215 -134
- package/dist/lib/compileAdk.d.ts +32 -0
- package/dist/lib/compileAdk.js +142 -0
- package/dist/lib/compileNative.d.ts +111 -0
- package/dist/lib/compileNative.js +278 -0
- package/dist/lib/config.d.ts +6 -5
- package/dist/lib/config.js +6 -5
- package/dist/lib/doctor.d.ts +28 -1
- package/dist/lib/doctor.js +64 -2
- package/dist/lib/index.d.ts +6 -1
- package/dist/lib/index.js +7 -1
- package/dist/lib/loadSyndicate.d.ts +18 -0
- package/dist/lib/memory/erase.d.ts +5 -3
- package/dist/lib/memory/erase.js +5 -2
- package/dist/lib/memory/namespace.d.ts +8 -1
- package/dist/lib/memory/namespace.js +12 -6
- package/dist/lib/memory/providers.js +2 -1
- package/dist/lib/memory/supabaseMemoryService.d.ts +37 -3
- package/dist/lib/memory/supabaseMemoryService.js +42 -2
- package/dist/lib/model.d.ts +47 -0
- package/dist/lib/model.js +37 -0
- package/dist/lib/models/adapterResolver.d.ts +115 -0
- package/dist/lib/models/adapterResolver.js +137 -0
- package/dist/lib/models/adkGeminiAdapter.d.ts +90 -0
- package/dist/lib/models/adkGeminiAdapter.js +369 -0
- package/dist/lib/models/adkShim.d.ts +111 -0
- package/dist/lib/models/adkShim.js +136 -0
- package/dist/lib/models/capabilities.d.ts +3 -0
- package/dist/lib/models/capabilities.js +33 -23
- package/dist/lib/models/chatCompletionsAdapter.d.ts +279 -0
- package/dist/lib/models/chatCompletionsAdapter.js +854 -0
- package/dist/lib/models/circuitBreaker.d.ts +42 -0
- package/dist/lib/models/circuitBreaker.js +70 -0
- package/dist/lib/models/claudeAdapter.d.ts +103 -0
- package/dist/lib/models/claudeAdapter.js +669 -0
- package/dist/lib/models/claudeLlm.d.ts +39 -62
- package/dist/lib/models/claudeLlm.js +45 -350
- package/dist/lib/models/claudeModels.d.ts +127 -0
- package/dist/lib/models/claudeModels.js +237 -0
- package/dist/lib/models/contract.d.ts +395 -0
- package/dist/lib/models/contract.js +39 -0
- package/dist/lib/models/endpoints.js +2 -1
- package/dist/lib/models/errorResponse.d.ts +50 -0
- package/dist/lib/models/errorResponse.js +48 -0
- package/dist/lib/models/fallback.d.ts +18 -11
- package/dist/lib/models/fallback.js +54 -43
- package/dist/lib/models/fallbackAdapter.d.ts +55 -0
- package/dist/lib/models/fallbackAdapter.js +62 -0
- package/dist/lib/models/gateway.js +2 -1
- package/dist/lib/models/gatewayAdapter.d.ts +51 -0
- package/dist/lib/models/gatewayAdapter.js +81 -0
- package/dist/lib/models/gatewayLlm.d.ts +12 -33
- package/dist/lib/models/gatewayLlm.js +16 -77
- package/dist/lib/models/geminiAdapter.d.ts +125 -0
- package/dist/lib/models/geminiAdapter.js +854 -0
- package/dist/lib/models/geminiState.d.ts +46 -0
- package/dist/lib/models/geminiState.js +51 -0
- package/dist/lib/models/genaiMapping.d.ts +283 -0
- package/dist/lib/models/genaiMapping.js +965 -0
- package/dist/lib/models/gptAdapter.d.ts +240 -0
- package/dist/lib/models/gptAdapter.js +804 -0
- package/dist/lib/models/gptLlm.d.ts +63 -123
- package/dist/lib/models/gptLlm.js +89 -603
- package/dist/lib/models/grokAdapter.d.ts +86 -0
- package/dist/lib/models/grokAdapter.js +142 -0
- package/dist/lib/models/grokLlm.d.ts +22 -45
- package/dist/lib/models/grokLlm.js +24 -69
- package/dist/lib/models/kimiAdapter.d.ts +157 -0
- package/dist/lib/models/kimiAdapter.js +203 -0
- package/dist/lib/models/kimiLlm.d.ts +15 -107
- package/dist/lib/models/kimiLlm.js +19 -155
- package/dist/lib/models/ollamaAdapter.d.ts +125 -0
- package/dist/lib/models/ollamaAdapter.js +155 -0
- package/dist/lib/models/ollamaLlm.d.ts +11 -107
- package/dist/lib/models/ollamaLlm.js +15 -153
- package/dist/lib/models/openAiCompatibleLlm.d.ts +73 -216
- package/dist/lib/models/openAiCompatibleLlm.js +109 -671
- package/dist/lib/models/providerState.d.ts +63 -0
- package/dist/lib/models/providerState.js +61 -0
- package/dist/lib/models/reasoning.d.ts +26 -0
- package/dist/lib/models/reasoning.js +59 -0
- package/dist/lib/models/registry.d.ts +62 -35
- package/dist/lib/models/registry.js +100 -124
- package/dist/lib/models/retry.d.ts +2 -1
- package/dist/lib/models/retry.js +3 -2
- package/dist/lib/models/schemaNormalize.d.ts +98 -0
- package/dist/lib/models/schemaNormalize.js +321 -1
- package/dist/lib/models/tracedGemini.d.ts +41 -0
- package/dist/lib/models/tracedGemini.js +98 -0
- package/dist/lib/models/urls.d.ts +11 -0
- package/dist/lib/models/urls.js +16 -0
- package/dist/lib/net/redirects.d.ts +21 -2
- package/dist/lib/net/redirects.js +11 -34
- package/dist/lib/observability/audit.d.ts +5 -1
- package/dist/lib/observability/lineage.d.ts +14 -0
- package/dist/lib/observability/lineage.js +33 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +10 -4
- package/dist/lib/observability/supabaseSpanExporter.js +17 -9
- package/dist/lib/observability/tracer.d.ts +26 -8
- package/dist/lib/observability/tracer.js +43 -18
- package/dist/lib/runtime/adkMemoryBridge.d.ts +46 -0
- package/dist/lib/runtime/adkMemoryBridge.js +68 -0
- package/dist/lib/runtime/adkSessionBridge.d.ts +78 -0
- package/dist/lib/runtime/adkSessionBridge.js +176 -0
- package/dist/lib/runtime/approvals.d.ts +5 -0
- package/dist/lib/runtime/approvals.js +39 -8
- package/dist/lib/runtime/credentials.d.ts +61 -0
- package/dist/lib/runtime/credentials.js +67 -0
- package/dist/lib/runtime/events.d.ts +229 -0
- package/dist/lib/runtime/events.js +274 -0
- package/dist/lib/runtime/logging.d.ts +32 -0
- package/dist/lib/runtime/logging.js +55 -0
- package/dist/lib/runtime/memoryService.d.ts +72 -0
- package/dist/lib/runtime/memoryService.js +25 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +209 -0
- package/dist/lib/runtime/native/agentLoop.js +705 -0
- package/dist/lib/runtime/native/compaction.d.ts +103 -0
- package/dist/lib/runtime/native/compaction.js +191 -0
- package/dist/lib/runtime/native/delegate.d.ts +151 -0
- package/dist/lib/runtime/native/delegate.js +256 -0
- package/dist/lib/runtime/native/history.d.ts +83 -0
- package/dist/lib/runtime/native/history.js +312 -0
- package/dist/lib/runtime/native/interrupts.d.ts +129 -0
- package/dist/lib/runtime/native/interrupts.js +310 -0
- package/dist/lib/runtime/native/request.d.ts +215 -0
- package/dist/lib/runtime/native/request.js +585 -0
- package/dist/lib/runtime/native/selfCorrection.d.ts +164 -0
- package/dist/lib/runtime/native/selfCorrection.js +327 -0
- package/dist/lib/runtime/native/step.d.ts +156 -0
- package/dist/lib/runtime/native/step.js +263 -0
- package/dist/lib/runtime/native/taskMode.d.ts +64 -0
- package/dist/lib/runtime/native/taskMode.js +126 -0
- package/dist/lib/runtime/native/telemetry.d.ts +75 -0
- package/dist/lib/runtime/native/telemetry.js +299 -0
- package/dist/lib/runtime/native/tempState.d.ts +31 -0
- package/dist/lib/runtime/native/tempState.js +56 -0
- package/dist/lib/runtime/nativeTurn.d.ts +86 -0
- package/dist/lib/runtime/nativeTurn.js +107 -0
- package/dist/lib/runtime/questions.d.ts +29 -7
- package/dist/lib/runtime/questions.js +47 -32
- package/dist/lib/runtime/runtimeFlag.d.ts +38 -0
- package/dist/lib/runtime/runtimeFlag.js +58 -0
- package/dist/lib/runtime/sessions.d.ts +166 -0
- package/dist/lib/runtime/sessions.js +201 -0
- package/dist/lib/runtime/syndicateTurn.d.ts +46 -5
- package/dist/lib/runtime/syndicateTurn.js +233 -36
- package/dist/lib/runtime/valueDepth.d.ts +23 -0
- package/dist/lib/runtime/valueDepth.js +41 -0
- package/dist/lib/session/supabaseSessionService.d.ts +65 -18
- package/dist/lib/session/supabaseSessionService.js +148 -105
- package/dist/lib/session/transcript.d.ts +69 -23
- package/dist/lib/session/transcript.js +105 -29
- package/dist/lib/skills.js +33 -7
- package/dist/lib/storage/postgres/credentialStore.d.ts +20 -0
- package/dist/lib/storage/postgres/credentialStore.js +82 -0
- package/dist/lib/storage/postgres/index.d.ts +12 -0
- package/dist/lib/storage/postgres/index.js +6 -1
- package/dist/lib/storage/postgres/sessionService.d.ts +44 -16
- package/dist/lib/storage/postgres/sessionService.js +128 -66
- package/dist/lib/syndicateSchema.d.ts +49 -0
- package/dist/lib/syndicateSchema.js +99 -9
- package/dist/lib/toolRegistry.d.ts +26 -2
- package/dist/lib/toolRegistry.js +73 -17
- package/dist/lib/tools/adkTool.d.ts +81 -0
- package/dist/lib/tools/adkTool.js +268 -0
- package/dist/lib/tools/auth.d.ts +118 -0
- package/dist/lib/tools/auth.js +89 -0
- package/dist/lib/tools/collectionsSearchTool.d.ts +8 -7
- package/dist/lib/tools/collectionsSearchTool.js +12 -27
- package/dist/lib/tools/credentialCipher.d.ts +58 -0
- package/dist/lib/tools/credentialCipher.js +116 -0
- package/dist/lib/tools/credentialStore.d.ts +77 -0
- package/dist/lib/tools/credentialStore.js +246 -0
- package/dist/lib/tools/examples.d.ts +43 -0
- package/dist/lib/tools/examples.js +62 -0
- package/dist/lib/tools/generateImageTool.d.ts +15 -6
- package/dist/lib/tools/generateImageTool.js +19 -29
- package/dist/lib/tools/inspectImageTool.d.ts +9 -6
- package/dist/lib/tools/inspectImageTool.js +13 -17
- package/dist/lib/tools/mcpToolFactory.d.ts +42 -1
- package/dist/lib/tools/mcpToolFactory.js +46 -47
- package/dist/lib/tools/memoryTools.d.ts +61 -0
- package/dist/lib/tools/memoryTools.js +119 -0
- package/dist/lib/tools/nativeTools.d.ts +38 -0
- package/dist/lib/tools/nativeTools.js +43 -0
- package/dist/lib/tools/oauthConsent.d.ts +181 -0
- package/dist/lib/tools/oauthConsent.js +330 -0
- package/dist/lib/tools/openapi/call.d.ts +108 -0
- package/dist/lib/tools/openapi/call.js +295 -0
- package/dist/lib/tools/openapi/parse.d.ts +147 -0
- package/dist/lib/tools/openapi/parse.js +570 -0
- package/dist/lib/tools/openapiTools.d.ts +25 -17
- package/dist/lib/tools/openapiTools.js +124 -121
- package/dist/lib/tools/scienceTools.d.ts +14 -14
- package/dist/lib/tools/skillToolset.d.ts +58 -87
- package/dist/lib/tools/skillToolset.js +61 -142
- package/dist/lib/tools/skills/env.d.ts +58 -0
- package/dist/lib/tools/skills/env.js +165 -0
- package/dist/lib/tools/skills/executor.d.ts +114 -0
- package/dist/lib/tools/skills/executor.js +331 -0
- package/dist/lib/tools/skills/frontmatter.d.ts +79 -0
- package/dist/lib/tools/skills/frontmatter.js +215 -0
- package/dist/lib/tools/skills/loader.d.ts +91 -0
- package/dist/lib/tools/skills/loader.js +257 -0
- package/dist/lib/tools/skills/tools.d.ts +187 -0
- package/dist/lib/tools/skills/tools.js +386 -0
- package/dist/lib/tools/taskTools.d.ts +15 -15
- package/dist/lib/tools/tool.d.ts +354 -0
- package/dist/lib/tools/tool.js +304 -0
- package/dist/lib/tools/toolContract.d.ts +89 -33
- package/dist/lib/tools/toolContract.js +104 -82
- package/dist/lib/tools/urlContextTool.d.ts +4 -1
- package/dist/lib/tools/urlContextTool.js +6 -2
- package/dist/lib/tools/webExtractTool.d.ts +3 -3
- package/dist/lib/tools/webExtractTool.js +2 -1
- package/dist/lib/tools/webSearchTool.d.ts +7 -10
- package/dist/lib/tools/webSearchTool.js +15 -60
- package/dist/lib/tools/wikiTools.d.ts +19 -19
- package/dist/lib/tools/wikiTools.js +13 -9
- package/dist/lib/tools/xApiSearchTool.d.ts +3 -3
- package/dist/lib/tools/xApiSearchTool.js +2 -1
- package/dist/lib/tools/xSearchTool.d.ts +8 -10
- package/dist/lib/tools/xSearchTool.js +12 -63
- package/dist/lib/tools/xaiSearchParams.d.ts +41 -0
- package/dist/lib/tools/xaiSearchParams.js +126 -0
- package/dist/lib/wiki/agentRun.d.ts +12 -0
- package/dist/lib/wiki/agentRun.js +74 -35
- package/dist/lib/wiki/entities.d.ts +12 -0
- package/dist/lib/wiki/entities.js +45 -3
- package/dist/lib/workflow/agentNode.d.ts +153 -0
- package/dist/lib/workflow/agentNode.js +365 -0
- package/dist/lib/workflow/graph.d.ts +178 -0
- package/dist/lib/workflow/graph.js +468 -0
- package/dist/lib/workflow/nodeEvents.d.ts +50 -0
- package/dist/lib/workflow/nodeEvents.js +101 -0
- package/dist/lib/workflow/pause.d.ts +122 -0
- package/dist/lib/workflow/pause.js +180 -0
- package/dist/lib/workflow/resume.d.ts +166 -0
- package/dist/lib/workflow/resume.js +441 -0
- package/dist/lib/workflow/route.d.ts +58 -0
- package/dist/lib/workflow/route.js +69 -0
- package/dist/lib/workflow/scheduler.d.ts +418 -0
- package/dist/lib/workflow/scheduler.js +756 -0
- package/dist/lib/workflow/toolNode.d.ts +127 -0
- package/dist/lib/workflow/toolNode.js +236 -0
- package/dist/lib/workflow/turn.d.ts +118 -0
- package/dist/lib/workflow/turn.js +216 -0
- package/dist/lib/workflow.d.ts +33 -11
- package/dist/lib/workflow.js +49 -27
- package/dist/lib/workflowConfig.d.ts +7 -2
- package/dist/lib/workflowConfig.js +7 -12
- package/dist/scripts/a2a_server.js +4 -3
- package/dist/scripts/assistant_worker.d.ts +2 -1
- package/dist/scripts/assistant_worker.js +9 -4
- package/dist/scripts/doctor.d.ts +3 -1
- package/dist/scripts/doctor.js +4 -2
- package/dist/scripts/syndicate_chat.js +19 -10
- package/package.json +9 -3
- package/skills/briefs/melchizedek-author.md +3 -2
- package/skills/briefs/melchizedek-models.md +13 -9
- package/skills/briefs/melchizedek.md +3 -3
- package/skills/melchizedek/SKILL.md +2 -1
- package/skills/melchizedek-author/SKILL.md +2 -1
- package/skills/melchizedek-author/assets/minimal.yaml +2 -0
- package/skills/melchizedek-models/SKILL.md +23 -6
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/workflow/resume.ts — a paused workflow resumes on the engine's own
|
|
3
|
+
* scheduler, as ADK 2.2 resumes it (ADR 0094).
|
|
4
|
+
*
|
|
5
|
+
* A walk that paused on an `ask_user` node (lib/workflow/pause.ts, ADR 0092)
|
|
6
|
+
* ended the turn `input-required`. The next message answers it. ADK keeps no
|
|
7
|
+
* workflow state between the two turns: it rebuilds every node's state from
|
|
8
|
+
* the session's events (workflow/utils/rehydration_utils.js) and walks the
|
|
9
|
+
* graph again from START. This module is that rebuild, with no ADK import,
|
|
10
|
+
* function for function:
|
|
11
|
+
*
|
|
12
|
+
* 1. THE RUN'S EVENTS (eventsForCurrentRun). The events of the current
|
|
13
|
+
* invocation, plus those of every paused run immediately before it, so
|
|
14
|
+
* a run paused twice resumes with both halves.
|
|
15
|
+
* 2. THE ANSWERS. A plain-text message answers the one open interrupt
|
|
16
|
+
* (run_node_as_invocation.js resumeInputsFromPlainText), and a
|
|
17
|
+
* function response named for an interrupt answers it explicitly
|
|
18
|
+
* (resolvedInterruptResponses: `{ result: x }` unwrapped, a structured
|
|
19
|
+
* reply checked against the request's `response_schema`, a reply to an
|
|
20
|
+
* unknown or already-answered interrupt refused with ADK's message).
|
|
21
|
+
* Together they are ADK's `ctx.resumeInputs`.
|
|
22
|
+
* 3. THE NODE STATES (reconstructNodeRuns). Per direct child of the
|
|
23
|
+
* workflow, its runs in order: the output, route and branch an event
|
|
24
|
+
* carried, the interrupts it raised, the answers to them, and the
|
|
25
|
+
* input recorded in `actions.agentState.input` (pause.ts writes it).
|
|
26
|
+
* 4. THE WALK'S INPUT (extractNodeInput): the message's text, else the
|
|
27
|
+
* message itself, as ADK hands its root workflow.
|
|
28
|
+
*
|
|
29
|
+
* `workflowResume` puts the four together; the scheduler
|
|
30
|
+
* (lib/workflow/scheduler.ts, `RunWorkflowOptions.resume`) then treats a
|
|
31
|
+
* node whose prior run has an output and no open interrupt as done
|
|
32
|
+
* (`isFastForwardable`), reruns a paused node that reruns on resume
|
|
33
|
+
* (`rerunsOnResume`) on its recorded input with the answers in
|
|
34
|
+
* `NodeRun.resumeInputs`, and walks on. An ask_user node rerun with an
|
|
35
|
+
* answer outputs `{ reply, input }` (pause.ts), as the FunctionNode
|
|
36
|
+
* lib/workflow.ts compiles does.
|
|
37
|
+
*
|
|
38
|
+
* TRUST: the answers are the person's words and the stored events are the
|
|
39
|
+
* session's; both are data the walk carries, never instructions this module
|
|
40
|
+
* acts on. A function call in an event the user authored raises nothing:
|
|
41
|
+
* only a node's event opens an interrupt. No regular expression reads input.
|
|
42
|
+
*/
|
|
43
|
+
import { z } from 'zod';
|
|
44
|
+
import { APPROVAL_REQUEST } from '../runtime/approvals.js';
|
|
45
|
+
import { CREDENTIAL_REQUEST } from '../runtime/credentials.js';
|
|
46
|
+
import { INPUT_REQUEST } from '../workflowConfig.js';
|
|
47
|
+
import { RESPONSE_SCHEMA_ARG } from './pause.js';
|
|
48
|
+
/** The key ADK unwraps a bare reply from: `{ result: <value> }`. */
|
|
49
|
+
const RESULT_KEY = 'result';
|
|
50
|
+
const partsOf = (event) => event.content?.parts ?? [];
|
|
51
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
52
|
+
const stringOr = (value) => (typeof value === 'string' ? value : undefined);
|
|
53
|
+
// ── 1. The run's events ──────────────────────────────────────────────────────
|
|
54
|
+
/**
|
|
55
|
+
* ADK's raisedInterrupt (requiresUserInput): the event carries an input,
|
|
56
|
+
* credential or confirmation request with an interrupt id.
|
|
57
|
+
*/
|
|
58
|
+
export function raisedInterrupt(event) {
|
|
59
|
+
for (const part of partsOf(event)) {
|
|
60
|
+
const call = part.functionCall;
|
|
61
|
+
if (!call?.name)
|
|
62
|
+
continue;
|
|
63
|
+
if (call.name !== INPUT_REQUEST && call.name !== CREDENTIAL_REQUEST && call.name !== APPROVAL_REQUEST)
|
|
64
|
+
continue;
|
|
65
|
+
const args = call.args ?? {};
|
|
66
|
+
const credential = call.name === CREDENTIAL_REQUEST;
|
|
67
|
+
// ADK camel-cases a credential request's keys before it reads functionCallId.
|
|
68
|
+
const id = call.id ?? stringOr(args.interruptId) ?? stringOr(args.functionCallId) ?? (credential ? stringOr(args.function_call_id) : undefined);
|
|
69
|
+
if (id)
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* ADK's eventsForCurrentRun: the events from the start of the current
|
|
76
|
+
* invocation, extended back over every run just before it that paused (an
|
|
77
|
+
* event without an invocation id belongs to the one before it; events
|
|
78
|
+
* without a node path neither start nor pause a run).
|
|
79
|
+
*/
|
|
80
|
+
export function eventsForCurrentRun(events, currentInvocationId) {
|
|
81
|
+
const priorRuns = [];
|
|
82
|
+
let currentStart = events.length;
|
|
83
|
+
let lastInvocationId;
|
|
84
|
+
for (let i = 0; i < events.length; i++) {
|
|
85
|
+
const event = events[i];
|
|
86
|
+
const invocationId = event.invocationId || lastInvocationId;
|
|
87
|
+
if (event.invocationId)
|
|
88
|
+
lastInvocationId = event.invocationId;
|
|
89
|
+
if (invocationId === currentInvocationId) {
|
|
90
|
+
if (currentStart === events.length)
|
|
91
|
+
currentStart = i;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!event.nodeInfo?.path)
|
|
95
|
+
continue;
|
|
96
|
+
let entry = priorRuns[priorRuns.length - 1];
|
|
97
|
+
if (!entry || entry.invocationId !== invocationId) {
|
|
98
|
+
entry = { start: i, invocationId, paused: false };
|
|
99
|
+
priorRuns.push(entry);
|
|
100
|
+
}
|
|
101
|
+
if (raisedInterrupt(event))
|
|
102
|
+
entry.paused = true;
|
|
103
|
+
}
|
|
104
|
+
let boundary = currentStart;
|
|
105
|
+
for (let i = priorRuns.length - 1; i >= 0 && priorRuns[i].paused; i--)
|
|
106
|
+
boundary = priorRuns[i].start;
|
|
107
|
+
return events.slice(boundary);
|
|
108
|
+
}
|
|
109
|
+
// ── 2. The answers ───────────────────────────────────────────────────────────
|
|
110
|
+
/** ADK's responseSchemasByInterruptId: the schema each input request declared. */
|
|
111
|
+
export function responseSchemasByInterruptId(events) {
|
|
112
|
+
const schemas = new Map();
|
|
113
|
+
for (const event of events) {
|
|
114
|
+
for (const part of partsOf(event)) {
|
|
115
|
+
const call = part.functionCall;
|
|
116
|
+
if (call?.name !== INPUT_REQUEST || !call.id)
|
|
117
|
+
continue;
|
|
118
|
+
const schema = call.args?.[RESPONSE_SCHEMA_ARG];
|
|
119
|
+
if (schema)
|
|
120
|
+
schemas.set(call.id, schema);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return schemas;
|
|
124
|
+
}
|
|
125
|
+
/** ADK's acceptsString: whether a JSON Schema admits a string. */
|
|
126
|
+
function acceptsString(schema) {
|
|
127
|
+
if (!isRecord(schema))
|
|
128
|
+
return false;
|
|
129
|
+
const type = schema.type;
|
|
130
|
+
if (type === 'string')
|
|
131
|
+
return true;
|
|
132
|
+
if (Array.isArray(type) && type.includes('string'))
|
|
133
|
+
return true;
|
|
134
|
+
for (const key of ['anyOf', 'oneOf']) {
|
|
135
|
+
const branches = schema[key];
|
|
136
|
+
if (Array.isArray(branches) && branches.some(acceptsString))
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
/** ADK's unwrapResponse: `{ result: x }` is x, a JSON string parsed unless the schema takes a string. */
|
|
142
|
+
export function unwrapResponse(response, responseSchema) {
|
|
143
|
+
if (isRecord(response) && Object.keys(response).length === 1 && RESULT_KEY in response) {
|
|
144
|
+
const value = response[RESULT_KEY];
|
|
145
|
+
if (typeof value !== 'string' || acceptsString(responseSchema))
|
|
146
|
+
return value;
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(value);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return response;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* ADK's interruptResponseMismatch: a structured reply (an object) that the
|
|
158
|
+
* request's schema refuses, as ADK's message; a scalar reply, or a schema
|
|
159
|
+
* zod cannot read, is not checked.
|
|
160
|
+
*/
|
|
161
|
+
export function interruptResponseMismatch(interruptId, value, jsonSchema) {
|
|
162
|
+
if (typeof value !== 'object' || value === null)
|
|
163
|
+
return undefined;
|
|
164
|
+
if (jsonSchema === null || typeof jsonSchema !== 'object')
|
|
165
|
+
return undefined;
|
|
166
|
+
let validator;
|
|
167
|
+
try {
|
|
168
|
+
validator = z.fromJSONSchema(jsonSchema);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const result = validator.safeParse(value);
|
|
174
|
+
if (result.success)
|
|
175
|
+
return undefined;
|
|
176
|
+
const issues = result.error.issues
|
|
177
|
+
.map((issue) => `${issue.message}${issue.path.length ? ` at '${issue.path.join('.')}'` : ''}`)
|
|
178
|
+
.join('; ');
|
|
179
|
+
return `The reply to interrupt '${interruptId}' does not match the responseSchema it declared: ${issues}. A structured reply must either match that schema, or wrap a bare value as {result: <value>}; a plain-text reply is accepted as-is and is not checked. The interrupt is still waiting, so you can answer it again.`;
|
|
180
|
+
}
|
|
181
|
+
const waitingList = (open) => open.size ? `Still waiting: ${[...open].map((id) => `'${id}'`).join(', ')}.` : 'This run has no interrupt waiting for an answer.';
|
|
182
|
+
function lastUserEvent(events) {
|
|
183
|
+
for (let i = events.length - 1; i >= 0; i--)
|
|
184
|
+
if (events[i].author === 'user')
|
|
185
|
+
return events[i];
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* ADK's resolvedInterruptResponses: every interrupt a function response in
|
|
190
|
+
* a user event answered, with the answer unwrapped. The newest user turn is
|
|
191
|
+
* held to account: a reply there to an interrupt that is not open, or one
|
|
192
|
+
* its schema refuses, throws ADK's message; an older one is skipped.
|
|
193
|
+
*/
|
|
194
|
+
export function resolvedInterruptResponses(events) {
|
|
195
|
+
const responseSchemas = responseSchemasByInterruptId(events);
|
|
196
|
+
const newestUserTurn = lastUserEvent(events);
|
|
197
|
+
const raised = new Set();
|
|
198
|
+
const open = new Set();
|
|
199
|
+
const resolved = new Map();
|
|
200
|
+
for (const event of events) {
|
|
201
|
+
if (event.author === 'user' && event.content?.parts) {
|
|
202
|
+
for (const part of event.content.parts) {
|
|
203
|
+
const response = part.functionResponse;
|
|
204
|
+
if (!response?.id)
|
|
205
|
+
continue;
|
|
206
|
+
if (!open.has(response.id)) {
|
|
207
|
+
if (response.name === INPUT_REQUEST && event === newestUserTurn) {
|
|
208
|
+
throw new Error(raised.has(response.id)
|
|
209
|
+
? `Interrupt '${response.id}' has already been answered, so this reply resolves nothing. Sending it again does not change the earlier answer, and it would otherwise be served as a new message, restarting the node that asked. ${waitingList(open)}`
|
|
210
|
+
: `The reply carries interrupt id '${response.id}', which does not match any interrupt this run raised. ${waitingList(open)} A reply must use the id from the 'adk_request_input' function call it answers. Nothing was resolved, so any waiting interrupt can still be answered.`);
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const schema = responseSchemas.get(response.id);
|
|
215
|
+
const value = unwrapResponse(response.response, schema);
|
|
216
|
+
const mismatch = interruptResponseMismatch(response.id, value, schema);
|
|
217
|
+
if (mismatch) {
|
|
218
|
+
if (event === newestUserTurn)
|
|
219
|
+
throw new Error(mismatch);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
open.delete(response.id);
|
|
223
|
+
resolved.set(response.id, value);
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
for (const id of event.longRunningToolIds ?? []) {
|
|
228
|
+
raised.add(id);
|
|
229
|
+
open.add(id);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return resolved;
|
|
233
|
+
}
|
|
234
|
+
// ── 3. The node states ───────────────────────────────────────────────────────
|
|
235
|
+
/** ADK's nodeNameFromPath: the last segment of a path, without its `@<run>` suffix. */
|
|
236
|
+
export function nodeNameFromPath(path) {
|
|
237
|
+
let start = 0;
|
|
238
|
+
for (let i = 0; i < path.length; i++)
|
|
239
|
+
if (path[i] === '.' || path[i] === '/')
|
|
240
|
+
start = i + 1;
|
|
241
|
+
const leaf = path.slice(start);
|
|
242
|
+
const at = leaf.indexOf('@');
|
|
243
|
+
return at === -1 ? leaf : leaf.slice(0, at);
|
|
244
|
+
}
|
|
245
|
+
/** ADK's directChildName: the child of `parentPath` an event's path names, or undefined for any other path. */
|
|
246
|
+
export function directChildName(path, parentPath) {
|
|
247
|
+
const prefix = `${parentPath}.`;
|
|
248
|
+
if (!path.startsWith(prefix))
|
|
249
|
+
return undefined;
|
|
250
|
+
const rest = path.slice(prefix.length);
|
|
251
|
+
if (rest.includes('.'))
|
|
252
|
+
return undefined;
|
|
253
|
+
const at = rest.indexOf('@');
|
|
254
|
+
return at === -1 ? rest : rest.slice(0, at);
|
|
255
|
+
}
|
|
256
|
+
const isRunClosed = (run) => run.output !== undefined || run.route !== undefined || run.interruptIds.size > 0;
|
|
257
|
+
function keyFn(parentPath) {
|
|
258
|
+
if (parentPath)
|
|
259
|
+
return (event) => (event.nodeInfo?.path ? directChildName(event.nodeInfo.path, parentPath) : undefined);
|
|
260
|
+
return (event) => (event.nodeInfo?.path ? nodeNameFromPath(event.nodeInfo.path) : event.author);
|
|
261
|
+
}
|
|
262
|
+
/** ADK's reconstructRuns: each key's runs, a run closing on an output, a route or an interrupt. */
|
|
263
|
+
function reconstructRuns(events, keyFor) {
|
|
264
|
+
const nodes = new Map();
|
|
265
|
+
const interruptOwner = new Map();
|
|
266
|
+
const resolved = resolvedInterruptResponses(events);
|
|
267
|
+
const currentRun = (name) => {
|
|
268
|
+
let runs = nodes.get(name);
|
|
269
|
+
if (!runs) {
|
|
270
|
+
runs = [];
|
|
271
|
+
nodes.set(name, runs);
|
|
272
|
+
}
|
|
273
|
+
const open = runs[runs.length - 1];
|
|
274
|
+
if (open && !isRunClosed(open))
|
|
275
|
+
return open;
|
|
276
|
+
const run = { interruptIds: new Set(), resolvedResponses: new Map() };
|
|
277
|
+
runs.push(run);
|
|
278
|
+
return run;
|
|
279
|
+
};
|
|
280
|
+
for (const event of events) {
|
|
281
|
+
if (event.author === 'user' && event.content?.parts) {
|
|
282
|
+
for (const part of event.content.parts) {
|
|
283
|
+
const id = part.functionResponse?.id;
|
|
284
|
+
if (id && interruptOwner.has(id) && resolved.has(id))
|
|
285
|
+
interruptOwner.get(id).resolvedResponses.set(id, resolved.get(id));
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const key = keyFor(event);
|
|
290
|
+
if (!key)
|
|
291
|
+
continue;
|
|
292
|
+
const run = currentRun(key);
|
|
293
|
+
if (event.output !== undefined) {
|
|
294
|
+
run.output = event.output;
|
|
295
|
+
run.branch = event.branch;
|
|
296
|
+
}
|
|
297
|
+
if (event.route !== undefined)
|
|
298
|
+
run.route = event.route;
|
|
299
|
+
for (const id of event.longRunningToolIds ?? []) {
|
|
300
|
+
run.interruptIds.add(id);
|
|
301
|
+
interruptOwner.set(id, run);
|
|
302
|
+
}
|
|
303
|
+
const agentState = event.actions?.agentState;
|
|
304
|
+
if (isRecord(agentState) && 'input' in agentState)
|
|
305
|
+
run.input = agentState.input;
|
|
306
|
+
}
|
|
307
|
+
return nodes;
|
|
308
|
+
}
|
|
309
|
+
/** ADK's reconstructNodeRuns: per direct child of `parentPath` (or per node name at the root), its runs in order. */
|
|
310
|
+
export function reconstructNodeRuns(events, parentPath) {
|
|
311
|
+
return reconstructRuns(events, keyFn(parentPath));
|
|
312
|
+
}
|
|
313
|
+
/** ADK's reconstructNodeStates: each key's runs merged into one state. */
|
|
314
|
+
export function reconstructNodeStates(events, parentPath) {
|
|
315
|
+
const merged = new Map();
|
|
316
|
+
for (const [key, runs] of reconstructRuns(events, keyFn(parentPath))) {
|
|
317
|
+
const node = { interruptIds: new Set(), resolvedResponses: new Map() };
|
|
318
|
+
for (const run of runs) {
|
|
319
|
+
if (run.output !== undefined) {
|
|
320
|
+
node.output = run.output;
|
|
321
|
+
node.branch = run.branch;
|
|
322
|
+
}
|
|
323
|
+
if (run.route !== undefined)
|
|
324
|
+
node.route = run.route;
|
|
325
|
+
if (run.input !== undefined)
|
|
326
|
+
node.input = run.input;
|
|
327
|
+
for (const id of run.interruptIds)
|
|
328
|
+
node.interruptIds.add(id);
|
|
329
|
+
for (const [id, value] of run.resolvedResponses)
|
|
330
|
+
node.resolvedResponses.set(id, value);
|
|
331
|
+
}
|
|
332
|
+
merged.set(key, node);
|
|
333
|
+
}
|
|
334
|
+
return merged;
|
|
335
|
+
}
|
|
336
|
+
/** ADK's isFastForwardable: the run produced an output or a route, and every interrupt it raised is answered. */
|
|
337
|
+
export function isFastForwardable(run) {
|
|
338
|
+
if (run.output === undefined && run.route === undefined)
|
|
339
|
+
return false;
|
|
340
|
+
for (const id of run.interruptIds)
|
|
341
|
+
if (!run.resolvedResponses.has(id))
|
|
342
|
+
return false;
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* ADK's rerunOnResume, by node kind as lib/workflow.ts compiles them: an
|
|
347
|
+
* agent (LlmAgent), an ask_user node (its FunctionNode sets it) and a map
|
|
348
|
+
* (ParallelWorker) run again on resume; a tool node, a join and a route
|
|
349
|
+
* step (BaseNode's default) do not, and resolve to their answers instead.
|
|
350
|
+
*/
|
|
351
|
+
export function rerunsOnResume(node) {
|
|
352
|
+
return node.kind === 'agent' || node.kind === 'ask_user' || node.kind === 'map';
|
|
353
|
+
}
|
|
354
|
+
// ── 4. The walk's input, and the plain-text answer ───────────────────────────
|
|
355
|
+
/** ADK's extractNodeInput: the message's text parts joined, else the message itself. */
|
|
356
|
+
export function workflowNodeInput(content) {
|
|
357
|
+
if (!content)
|
|
358
|
+
return undefined;
|
|
359
|
+
const texts = (content.parts ?? []).filter((p) => typeof p.text === 'string');
|
|
360
|
+
if (texts.length > 0)
|
|
361
|
+
return texts.map((p) => p.text).join('');
|
|
362
|
+
return content;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* ADK's resumeInputsFromPlainText: a message of text parts only answers the
|
|
366
|
+
* one interrupt left open in the run's events; with none or several open,
|
|
367
|
+
* it answers nothing (it is the walk's new input).
|
|
368
|
+
*/
|
|
369
|
+
export function resumeInputsFromPlainText(content, events, invocationId) {
|
|
370
|
+
const parts = content?.parts ?? [];
|
|
371
|
+
if (!(parts.length > 0 && parts.every((p) => typeof p.text === 'string')))
|
|
372
|
+
return {};
|
|
373
|
+
const text = parts.map((p) => p.text).join('');
|
|
374
|
+
const pending = new Set();
|
|
375
|
+
for (const state of reconstructNodeStates(eventsForCurrentRun(events, invocationId)).values()) {
|
|
376
|
+
for (const id of state.interruptIds)
|
|
377
|
+
if (!state.resolvedResponses.has(id))
|
|
378
|
+
pending.add(id);
|
|
379
|
+
}
|
|
380
|
+
if (pending.size !== 1)
|
|
381
|
+
return {};
|
|
382
|
+
const [id] = pending;
|
|
383
|
+
return { [id]: text };
|
|
384
|
+
}
|
|
385
|
+
// ── What the native walk cannot resume yet ───────────────────────────────────
|
|
386
|
+
/**
|
|
387
|
+
* A pause the native walk cannot resume: one raised inside an agent node
|
|
388
|
+
* other than an approval (an `ask_user` tool call, a credential request) or
|
|
389
|
+
* inside a map item. ADK resumes those inside the node (the agent's own
|
|
390
|
+
* history, the ParallelWorker's item); the native agent node pauses only on
|
|
391
|
+
* an approval (lib/workflow/agentNode.ts, ADR 0098), so it cannot pick the
|
|
392
|
+
* others up. Resuming the walk anyway would run the node again from its
|
|
393
|
+
* input and ask again, so the resume refuses by name instead.
|
|
394
|
+
*/
|
|
395
|
+
export class UnsupportedWorkflowResumeError extends Error {
|
|
396
|
+
/** The node path the pause was raised at. */
|
|
397
|
+
nodePath;
|
|
398
|
+
constructor(nodePath, why) {
|
|
399
|
+
super(`Cannot resume the workflow on the native runtime: the pause at '${nodePath}' was raised ${why}, which only an ask_user node's pause or an agent node's approval supports here.`);
|
|
400
|
+
this.name = 'UnsupportedWorkflowResumeError';
|
|
401
|
+
this.nodePath = nodePath;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Throws UnsupportedWorkflowResumeError for the first interrupt in the run's
|
|
406
|
+
* events that is neither an ask_user node's input request nor an agent
|
|
407
|
+
* node's approval request at a direct child of the workflow (the workflow's
|
|
408
|
+
* own record aside).
|
|
409
|
+
*/
|
|
410
|
+
export function assertResumable(runEvents, workflowPath) {
|
|
411
|
+
for (const event of runEvents) {
|
|
412
|
+
if (event.author === 'user' || !(event.longRunningToolIds?.length))
|
|
413
|
+
continue;
|
|
414
|
+
const path = event.nodeInfo?.path ?? '';
|
|
415
|
+
if (path === workflowPath)
|
|
416
|
+
continue;
|
|
417
|
+
if (directChildName(path, workflowPath) === undefined)
|
|
418
|
+
throw new UnsupportedWorkflowResumeError(path || String(event.author ?? ''), 'inside a nested node (a map item)');
|
|
419
|
+
const resumable = partsOf(event).some((p) => p.functionCall?.name === INPUT_REQUEST || p.functionCall?.name === APPROVAL_REQUEST);
|
|
420
|
+
if (!resumable)
|
|
421
|
+
throw new UnsupportedWorkflowResumeError(path, 'inside an agent node');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Everything a walk needs to resume as ADK's does: the new message as the
|
|
426
|
+
* walk's input, the prior runs of each node of the workflow, and the
|
|
427
|
+
* answers. On a session with nothing paused, every node runs fresh (its
|
|
428
|
+
* prior runs belong to finished invocations, which the run's events leave
|
|
429
|
+
* out). Throws ADK's message for a reply that answers nothing open, and
|
|
430
|
+
* UnsupportedWorkflowResumeError for a pause only ADK can resume.
|
|
431
|
+
*/
|
|
432
|
+
export function workflowResume(request) {
|
|
433
|
+
const resumeInputs = resumeInputsFromPlainText(request.userContent, request.events, request.invocationId);
|
|
434
|
+
const runEvents = eventsForCurrentRun(request.events, request.invocationId);
|
|
435
|
+
assertResumable(runEvents, request.workflowPath);
|
|
436
|
+
const priorRuns = reconstructNodeRuns(runEvents, request.workflowPath || undefined);
|
|
437
|
+
// ADK's applyResumeInputs: the explicit answers, over the plain-text one.
|
|
438
|
+
for (const [id, value] of resolvedInterruptResponses(runEvents))
|
|
439
|
+
resumeInputs[id] = value;
|
|
440
|
+
return { input: workflowNodeInput(request.userContent), resume: { priorRuns, resumeInputs } };
|
|
441
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/workflow/route.ts — the route a workflow node's output takes, and the
|
|
3
|
+
* event a route step stores (ADR 0030, ADR 0090).
|
|
4
|
+
*
|
|
5
|
+
* ADK's TypeScript port never derives a route from an agent's output, so a
|
|
6
|
+
* routing map after a node gets a hidden step, `<Node>__route`, that reads
|
|
7
|
+
* the route from the output and re-emits the output with it (lib/workflow.ts
|
|
8
|
+
* compiles it as a FunctionNode; lib/workflow/scheduler.ts runs it itself).
|
|
9
|
+
* The rule is one function both runtimes call, `routeOf`:
|
|
10
|
+
*
|
|
11
|
+
* - an object output (an agent with an output schema, a join, a tool's
|
|
12
|
+
* result): its `route_key` property, `route` by default, trimmed, `''`
|
|
13
|
+
* when the property is absent or null;
|
|
14
|
+
* - a string output: the text, trimmed. A JSON object an agent WITHOUT an
|
|
15
|
+
* output schema writes is text, as ADK keeps it (run_llm_agent_as_node's
|
|
16
|
+
* maybeSetOutput parses only with a schema), so it routes on its whole
|
|
17
|
+
* text, which usually takes the `default` edge;
|
|
18
|
+
* - anything else: its string form, trimmed; `''` for undefined or null.
|
|
19
|
+
*
|
|
20
|
+
* The scheduler then matches the route against the keys in ADK's spelling
|
|
21
|
+
* and takes the `default` edge when no key matched (scheduler.ts nextNodes),
|
|
22
|
+
* so the default catches every route no key names, `''` included.
|
|
23
|
+
*
|
|
24
|
+
* `routeStepEvent` is the event ADK's route step stores, so a session the
|
|
25
|
+
* native walk writes holds what ADK's holds: authored by the step, the
|
|
26
|
+
* output and the route, the step's node path, no content.
|
|
27
|
+
*
|
|
28
|
+
* No ADK import: the ADK path (lib/workflow.ts) imports `routeOf` from here
|
|
29
|
+
* through lib/workflowConfig.ts.
|
|
30
|
+
*/
|
|
31
|
+
import type { TurnEvent } from '../runtime/events.ts';
|
|
32
|
+
/** The route_key a routing map reads when the node sets none. */
|
|
33
|
+
export declare const DEFAULT_ROUTE_PROPERTY = "route";
|
|
34
|
+
/**
|
|
35
|
+
* The route a node's output takes: the `routeKey` property of an object
|
|
36
|
+
* output, else the trimmed text, else `''`. Linear in the output's size; no
|
|
37
|
+
* regular expression.
|
|
38
|
+
*/
|
|
39
|
+
export declare function routeOf(output: unknown, routeKey?: string): string;
|
|
40
|
+
/** Where a route step ran: the scheduler's node_end for it. */
|
|
41
|
+
export interface RouteStepRun {
|
|
42
|
+
/** The step's name, `<Node>__route`. */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Its node path, `<workflow>.<Node>__route`. */
|
|
45
|
+
path: string;
|
|
46
|
+
branch: string | undefined;
|
|
47
|
+
invocationId: string;
|
|
48
|
+
/** The output it re-emits: its predecessor's. */
|
|
49
|
+
output: unknown;
|
|
50
|
+
/** The route it emitted (routeOf). */
|
|
51
|
+
route: unknown;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The event ADK stores for a route step: its FunctionNode's createEvent
|
|
55
|
+
* (author, invocation, branch, output, route), stamped by the node runner
|
|
56
|
+
* with the step's path and `outputFor` (node_runner.js enrichEvent).
|
|
57
|
+
*/
|
|
58
|
+
export declare function routeStepEvent(run: RouteStepRun): TurnEvent;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/workflow/route.ts — the route a workflow node's output takes, and the
|
|
3
|
+
* event a route step stores (ADR 0030, ADR 0090).
|
|
4
|
+
*
|
|
5
|
+
* ADK's TypeScript port never derives a route from an agent's output, so a
|
|
6
|
+
* routing map after a node gets a hidden step, `<Node>__route`, that reads
|
|
7
|
+
* the route from the output and re-emits the output with it (lib/workflow.ts
|
|
8
|
+
* compiles it as a FunctionNode; lib/workflow/scheduler.ts runs it itself).
|
|
9
|
+
* The rule is one function both runtimes call, `routeOf`:
|
|
10
|
+
*
|
|
11
|
+
* - an object output (an agent with an output schema, a join, a tool's
|
|
12
|
+
* result): its `route_key` property, `route` by default, trimmed, `''`
|
|
13
|
+
* when the property is absent or null;
|
|
14
|
+
* - a string output: the text, trimmed. A JSON object an agent WITHOUT an
|
|
15
|
+
* output schema writes is text, as ADK keeps it (run_llm_agent_as_node's
|
|
16
|
+
* maybeSetOutput parses only with a schema), so it routes on its whole
|
|
17
|
+
* text, which usually takes the `default` edge;
|
|
18
|
+
* - anything else: its string form, trimmed; `''` for undefined or null.
|
|
19
|
+
*
|
|
20
|
+
* The scheduler then matches the route against the keys in ADK's spelling
|
|
21
|
+
* and takes the `default` edge when no key matched (scheduler.ts nextNodes),
|
|
22
|
+
* so the default catches every route no key names, `''` included.
|
|
23
|
+
*
|
|
24
|
+
* `routeStepEvent` is the event ADK's route step stores, so a session the
|
|
25
|
+
* native walk writes holds what ADK's holds: authored by the step, the
|
|
26
|
+
* output and the route, the step's node path, no content.
|
|
27
|
+
*
|
|
28
|
+
* No ADK import: the ADK path (lib/workflow.ts) imports `routeOf` from here
|
|
29
|
+
* through lib/workflowConfig.ts.
|
|
30
|
+
*/
|
|
31
|
+
import { createTurnEvent } from '../runtime/events.js';
|
|
32
|
+
/** The route_key a routing map reads when the node sets none. */
|
|
33
|
+
export const DEFAULT_ROUTE_PROPERTY = 'route';
|
|
34
|
+
/**
|
|
35
|
+
* The route a node's output takes: the `routeKey` property of an object
|
|
36
|
+
* output, else the trimmed text, else `''`. Linear in the output's size; no
|
|
37
|
+
* regular expression.
|
|
38
|
+
*/
|
|
39
|
+
export function routeOf(output, routeKey = DEFAULT_ROUTE_PROPERTY) {
|
|
40
|
+
if (output && typeof output === 'object' && !Array.isArray(output)) {
|
|
41
|
+
const value = output[routeKey];
|
|
42
|
+
if (value === undefined || value === null)
|
|
43
|
+
return '';
|
|
44
|
+
return String(value).trim();
|
|
45
|
+
}
|
|
46
|
+
if (typeof output === 'string')
|
|
47
|
+
return output.trim();
|
|
48
|
+
return output === undefined || output === null ? '' : String(output).trim();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The event ADK stores for a route step: its FunctionNode's createEvent
|
|
52
|
+
* (author, invocation, branch, output, route), stamped by the node runner
|
|
53
|
+
* with the step's path and `outputFor` (node_runner.js enrichEvent).
|
|
54
|
+
*/
|
|
55
|
+
export function routeStepEvent(run) {
|
|
56
|
+
const event = createTurnEvent({
|
|
57
|
+
author: run.name,
|
|
58
|
+
invocationId: run.invocationId,
|
|
59
|
+
...(run.branch !== undefined ? { branch: run.branch } : {}),
|
|
60
|
+
});
|
|
61
|
+
if (run.output !== undefined)
|
|
62
|
+
event.output = run.output;
|
|
63
|
+
if (run.route !== undefined)
|
|
64
|
+
event.route = run.route;
|
|
65
|
+
// What enrichNodeEvent (lib/workflow/toolNode.ts) writes on this event, kept here so this module stays a leaf:
|
|
66
|
+
// lib/workflowConfig.ts re-exports routeOf, and the schema and the ADK path import it.
|
|
67
|
+
event.nodeInfo = { path: run.path, ...(run.output !== undefined ? { outputFor: [run.path] } : {}) };
|
|
68
|
+
return event;
|
|
69
|
+
}
|