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,705 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/runtime/native/agentLoop.ts — the native loop for one agent: a model
|
|
3
|
+
* step, the step's tool calls, their results fed back, until the model
|
|
4
|
+
* stops calling tools or the turn stops it (ADR 0045, ADR 0066).
|
|
5
|
+
*
|
|
6
|
+
* WHY this file exists:
|
|
7
|
+
* On the ADK runtime this is LlmAgent.runAsyncImpl: run one step
|
|
8
|
+
* (lib/runtime/native/step.ts is the native step), run the answer's
|
|
9
|
+
* function calls (ADK's handleFunctionCallList), store the response
|
|
10
|
+
* event, and step again unless the last event is a final response. A
|
|
11
|
+
* session either runtime wrote must be one the other continues, so every
|
|
12
|
+
* event this loop stores is the one ADK stores for the same answer and the
|
|
13
|
+
* same tool results, ids and times aside. tests/nativeLoop.test.ts runs
|
|
14
|
+
* the boundary suite's single-agent cases both ways and compares.
|
|
15
|
+
*
|
|
16
|
+
* ONE STEP, AS ADK RUNS IT:
|
|
17
|
+
* 1. The model step (runModelStep). Its partial events are yielded as they
|
|
18
|
+
* arrive and stored nowhere, so drainAgentStream hands their text to
|
|
19
|
+
* onTextDelta exactly as it does on the ADK path. With a
|
|
20
|
+
* `fallback_model`, the step runs once per leaf adapter (ADR 0053): a
|
|
21
|
+
* provider-side failure before anything was produced is not stored,
|
|
22
|
+
* and the fallback answers the same request under its own model id, by
|
|
23
|
+
* ADK's FallbackLlm's rules and the shared circuit breaker (ADR 0044).
|
|
24
|
+
* 2. The answer's calls run in parallel, each with its own context (its
|
|
25
|
+
* own state delta and actions). Results are kept in call order:
|
|
26
|
+
* - a call naming no declared tool answers
|
|
27
|
+
* `{ error: "Function <name> is not found in the toolsDict." }`;
|
|
28
|
+
* - a tool that throws answers `{ error: "Error in tool '<name>': …" }`,
|
|
29
|
+
* the text ADK's FunctionTool writes (lib/tools/adkTool.ts);
|
|
30
|
+
* - with self-correction's tool side on (retries.tool_errors, default
|
|
31
|
+
* 3), those two answer with reflection guidance instead, counted per
|
|
32
|
+
* tool in call order (lib/runtime/native/selfCorrection.ts);
|
|
33
|
+
* - a tool that requires approval and has none asks for it, as
|
|
34
|
+
* FunctionTool's gate does: `{ error: APPROVAL_TEXTS.pending }`,
|
|
35
|
+
* `skipSummarization`, and the request under the call's id;
|
|
36
|
+
* - a long-running tool (ask_user) that returns nothing answers
|
|
37
|
+
* nothing; its actions, when it set any, make an event of their own;
|
|
38
|
+
* - a result that is not an object is wrapped `{ result }`, an array
|
|
39
|
+
* `{ results }`, as ADK wraps them;
|
|
40
|
+
* - a result nested deeper than MAX_VALUE_DEPTH levels answers
|
|
41
|
+
* `{ error: TOO_DEEP_RESULT }` instead: every later reader of the
|
|
42
|
+
* session would overflow its stack on it (lib/runtime/valueDepth.ts,
|
|
43
|
+
* ADR 0101). ADK keeps it.
|
|
44
|
+
* One call's response is stored as its own event; several are merged
|
|
45
|
+
* into one, parts in call order and actions merged (ADK's
|
|
46
|
+
* mergeParallelFunctionResponseEvents). ADK runs the calls one after
|
|
47
|
+
* another and lets a call read the state an earlier call of the same
|
|
48
|
+
* step wrote; here each call reads the state as the step left it.
|
|
49
|
+
* 3. An approval request ends the run: the response is NOT stored; in its
|
|
50
|
+
* place the event ADK stores, a call to `adk_request_confirmation`
|
|
51
|
+
* (args: the original call and the confirmation; an `adk-` id listed in
|
|
52
|
+
* longRunningToolIds), carrying the response's actions. The answer
|
|
53
|
+
* resumes it: before each step, lib/runtime/native/interrupts.ts runs
|
|
54
|
+
* the pinned call the latest user message approves or refuses, as ADK's
|
|
55
|
+
* request-confirmation processor does, and its response is stored
|
|
56
|
+
* before the request is built.
|
|
57
|
+
* 4. The agent's outputKey is written into each final event's stateDelta
|
|
58
|
+
* before it is stored (ADK's maybeSaveOutputToState, case for case).
|
|
59
|
+
* The `temp:` keys of every event stored are kept for the rest of the
|
|
60
|
+
* run, beside the session and never in it, and each later step and
|
|
61
|
+
* call reads them (lib/runtime/native/tempState.ts).
|
|
62
|
+
* 5. The loop goes on unless the step's last event is final (ADK's
|
|
63
|
+
* isFinalResponse), the turn stopped the step, or the step stored
|
|
64
|
+
* nothing. A model call that waits on a person (ask_user) is final: its
|
|
65
|
+
* model event lists the call in longRunningToolIds, and the run ends
|
|
66
|
+
* with no response to it, the call pending. The answer resumes it: the
|
|
67
|
+
* turn runner stores the person's next message as the call's function
|
|
68
|
+
* response, and the next run's first step reads the call and its answer
|
|
69
|
+
* from the history (history.ts), as ADK's content processor does. No
|
|
70
|
+
* request processor acts on it (ADR 0079).
|
|
71
|
+
*
|
|
72
|
+
* DELEGATION: a call to a subagent tool runs the subagent as its own child
|
|
73
|
+
* loop, as ADK's AgentTool runs it (lib/runtime/native/delegate.ts, ADR 0074).
|
|
74
|
+
*
|
|
75
|
+
* SELF-CORRECTION (ADR 0034, ADR 0075) is ADK's reflect-and-retry plugins,
|
|
76
|
+
* ported to lib/runtime/native/selfCorrection.ts: the step declares the
|
|
77
|
+
* reflection tool and passes each response through its model side, and
|
|
78
|
+
* each call's error or answer goes through its tool side. On by default,
|
|
79
|
+
* as runSyndicateTurn installs the plugins by default.
|
|
80
|
+
*
|
|
81
|
+
* COMPACTION (ADR 0033): with the agent's `context:`, each step may first
|
|
82
|
+
* store ADK's compacted event, written by the summary model
|
|
83
|
+
* (lib/runtime/native/compaction.ts). It is yielded like any stored event
|
|
84
|
+
* and is not the run's `lastEvent`.
|
|
85
|
+
*
|
|
86
|
+
* TASK MODE (ADR 0033, ADR 0081): finish_task is an own tool the request
|
|
87
|
+
* declares; a run marked `taskNode` ends on its successful answer
|
|
88
|
+
* (lib/runtime/native/taskMode.ts).
|
|
89
|
+
*
|
|
90
|
+
* WORKFLOW NODES (ADR 0090): a run for a workflow agent node passes
|
|
91
|
+
* `nodeStamp`, which writes the node's output and path on each event before
|
|
92
|
+
* it is stored (lib/workflow/agentNode.ts).
|
|
93
|
+
*
|
|
94
|
+
* CONSENT (WS6-3b, ADR 0085): with `credentials`, each call's
|
|
95
|
+
* `accessToken(provider)` reads the user's grant (ADR 0072). With `consent`,
|
|
96
|
+
* a call whose provider the user has not granted asks for it
|
|
97
|
+
* (`requestCredential`, or `accessToken` by itself): the call answers
|
|
98
|
+
* CONSENT_TEXTS.pending, ADK's `adk_request_credential` event is stored
|
|
99
|
+
* before the response (ADK's generateAuthEvent), and the run ends paused on
|
|
100
|
+
* it. The flow's secrets stay in the consent step (lib/tools/oauthConsent.ts).
|
|
101
|
+
* Before each step, a credential request the latest user message answers
|
|
102
|
+
* runs its paused call again (interrupts.ts grantedCalls, ADK's auth
|
|
103
|
+
* preprocessor), ahead of the approval resume.
|
|
104
|
+
*
|
|
105
|
+
* NOT HERE (later tickets): transfer_to_agent (no compiled syndicate sets
|
|
106
|
+
* subAgents). The run's spans (agent.invoke, model.call,
|
|
107
|
+
* tool.execute) are lib/runtime/native/telemetry.ts.
|
|
108
|
+
*
|
|
109
|
+
* ADK stays out of this file: an ADK tool an agent still lists during the
|
|
110
|
+
* dual period (a registry FunctionTool, the skills toolset's tools) is run
|
|
111
|
+
* through its runAsync with a context shaped like ADK's, by shape.
|
|
112
|
+
*/
|
|
113
|
+
import { randomUUID } from 'node:crypto';
|
|
114
|
+
import { circuitOpen, recordFailure, recordSuccess } from '../../models/circuitBreaker.js';
|
|
115
|
+
import { providerForModel } from '../../models/providerMap.js';
|
|
116
|
+
import { resolveAdapter } from '../../models/registry.js';
|
|
117
|
+
import { toContractJsonSchema } from '../../models/schemaNormalize.js';
|
|
118
|
+
import { z } from 'zod';
|
|
119
|
+
import { ToolCredentialError, toolAccessToken } from '../../tools/auth.js';
|
|
120
|
+
import { APPROVAL_TEXTS, CONSENT_TEXTS, isTool } from '../../tools/tool.js';
|
|
121
|
+
import { createEventActions, createTurnEvent, getFunctionCalls, getFunctionResponses, isFinal } from '../events.js';
|
|
122
|
+
import { MAX_VALUE_DEPTH, nestedDeeperThan } from '../valueDepth.js';
|
|
123
|
+
import { currentTurnSignal } from '../turnControl.js';
|
|
124
|
+
import { compactBeforeStep } from './compaction.js';
|
|
125
|
+
import { ADK_CALL_ID_PREFIX } from './history.js';
|
|
126
|
+
import { runSubagent, runWorkflowSubagent, subagentOf, workflowSubagentOf } from './delegate.js';
|
|
127
|
+
import { approvedCalls, grantedCalls } from './interrupts.js';
|
|
128
|
+
import { SelfCorrection } from './selfCorrection.js';
|
|
129
|
+
import { eitherSignal, runModelStep, stopOf } from './step.js';
|
|
130
|
+
import { taskNodeRun } from './taskMode.js';
|
|
131
|
+
import { createRunTempState, withStateOverlay } from './tempState.js';
|
|
132
|
+
import { traceAgentInvocation, traceModelCall, traceToolCall } from './telemetry.js';
|
|
133
|
+
/** ADK's own ceiling on model calls in one run (RunConfig.maxLlmCalls); the turn's max_steps is lower. */
|
|
134
|
+
const MAX_LLM_CALLS = 500;
|
|
135
|
+
// ── Events as ADK makes them ─────────────────────────────────────────────────
|
|
136
|
+
const REQUEST_CONFIRMATION = 'adk_request_confirmation';
|
|
137
|
+
const REQUEST_CREDENTIAL = 'adk_request_credential';
|
|
138
|
+
const isEmptyRecord = (r) => !r || Object.keys(r).length === 0;
|
|
139
|
+
/** Sets an own property even for a key such as `__proto__`. */
|
|
140
|
+
function setOwn(target, key, value) {
|
|
141
|
+
Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
|
|
142
|
+
}
|
|
143
|
+
/** ADK's isDefaultEventActions. */
|
|
144
|
+
function isDefaultActions(a) {
|
|
145
|
+
return (isEmptyRecord(a.stateDelta) &&
|
|
146
|
+
isEmptyRecord(a.artifactDelta) &&
|
|
147
|
+
isEmptyRecord(a.requestedAuthConfigs) &&
|
|
148
|
+
isEmptyRecord(a.requestedToolConfirmations) &&
|
|
149
|
+
a.skipSummarization === undefined &&
|
|
150
|
+
a.transferToAgent === undefined &&
|
|
151
|
+
a.escalate === undefined);
|
|
152
|
+
}
|
|
153
|
+
/** Copies `from`'s own keys onto `to` as own keys: Object.assign would re-parent `to` for a key `__proto__` (a model-chosen call id). */
|
|
154
|
+
function assignOwn(to, from) {
|
|
155
|
+
for (const [k, v] of Object.entries(from))
|
|
156
|
+
setOwn(to, k, v);
|
|
157
|
+
}
|
|
158
|
+
/** ADK's mergeEventActions: dictionaries merged in order, the later flag winning. */
|
|
159
|
+
function mergeActions(sources) {
|
|
160
|
+
const out = createEventActions();
|
|
161
|
+
for (const s of sources) {
|
|
162
|
+
if (s.stateDelta)
|
|
163
|
+
assignOwn(out.stateDelta, s.stateDelta);
|
|
164
|
+
if (s.artifactDelta)
|
|
165
|
+
assignOwn(out.artifactDelta, s.artifactDelta);
|
|
166
|
+
if (s.requestedAuthConfigs)
|
|
167
|
+
assignOwn(out.requestedAuthConfigs, s.requestedAuthConfigs);
|
|
168
|
+
if (s.requestedToolConfirmations)
|
|
169
|
+
assignOwn(out.requestedToolConfirmations, s.requestedToolConfirmations);
|
|
170
|
+
if (s.skipSummarization !== undefined)
|
|
171
|
+
out.skipSummarization = s.skipSummarization;
|
|
172
|
+
if (s.transferToAgent !== undefined)
|
|
173
|
+
out.transferToAgent = s.transferToAgent;
|
|
174
|
+
if (s.escalate !== undefined)
|
|
175
|
+
out.escalate = s.escalate;
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
// ── outputKey (ADK's maybeSaveOutputToState) ─────────────────────────────────
|
|
180
|
+
const validators = new WeakMap();
|
|
181
|
+
/** The output schema as a validator, as ADK reads it (z.fromJSONSchema); null when it cannot be read. */
|
|
182
|
+
function validatorFor(schema) {
|
|
183
|
+
let v = validators.get(schema);
|
|
184
|
+
if (v !== undefined)
|
|
185
|
+
return v;
|
|
186
|
+
try {
|
|
187
|
+
v = z.fromJSONSchema(toContractJsonSchema(schema));
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
v = null;
|
|
191
|
+
}
|
|
192
|
+
validators.set(schema, v);
|
|
193
|
+
return v;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Writes the agent's final answer into `event`'s stateDelta under its
|
|
197
|
+
* outputKey: the text of its parts; with an output schema, the parsed and
|
|
198
|
+
* validated JSON (the text itself when it does not parse; the parsed value
|
|
199
|
+
* when it does not validate).
|
|
200
|
+
*/
|
|
201
|
+
export function saveOutput(agent, event) {
|
|
202
|
+
if (event.author !== agent.name || !agent.outputKey || !isFinal(event))
|
|
203
|
+
return;
|
|
204
|
+
const parts = event.content?.parts;
|
|
205
|
+
if (!parts?.length)
|
|
206
|
+
return;
|
|
207
|
+
const text = parts.map((p) => (p.text ? p.text : '')).join('');
|
|
208
|
+
const delta = (event.actions.stateDelta ??= {});
|
|
209
|
+
let result = text;
|
|
210
|
+
if (agent.outputSchema) {
|
|
211
|
+
if (!text.trim())
|
|
212
|
+
return;
|
|
213
|
+
let parsed;
|
|
214
|
+
try {
|
|
215
|
+
parsed = JSON.parse(text);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
setOwn(delta, agent.outputKey, text);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const validator = validatorFor(agent.outputSchema);
|
|
223
|
+
result = validator ? validator.parse(parsed) : parsed;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
result = parsed;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
setOwn(delta, agent.outputKey, result);
|
|
230
|
+
}
|
|
231
|
+
function callContext(scope, functionCallId, confirmation) {
|
|
232
|
+
const { agent, ctx, stateBase } = scope;
|
|
233
|
+
const session = ctx.session;
|
|
234
|
+
const actions = createEventActions();
|
|
235
|
+
const delta = actions.stateDelta;
|
|
236
|
+
const state = {
|
|
237
|
+
get: (key, fallback) => (Object.hasOwn(delta, key) ? delta[key] : Object.hasOwn(stateBase, key) ? stateBase[key] : fallback),
|
|
238
|
+
set: (key, value) => setOwn(delta, key, value),
|
|
239
|
+
has: (key) => Object.hasOwn(delta, key) || Object.hasOwn(stateBase, key),
|
|
240
|
+
};
|
|
241
|
+
const memory = ctx.memory;
|
|
242
|
+
// ADK's Context.requestCredential, with the flow held server-side (ADR 0085): the request, keyed by the call's id.
|
|
243
|
+
const consent = ctx.consent;
|
|
244
|
+
const requestCredential = consent && functionCallId
|
|
245
|
+
? async (provider) => {
|
|
246
|
+
const request = await consent.begin({ appName: session.appName, userId: session.userId, sessionId: session.id, functionCallId, provider });
|
|
247
|
+
// An own key, as every delta: a call id such as `__proto__` names a request, never a prototype.
|
|
248
|
+
setOwn(actions.requestedAuthConfigs, functionCallId, request.authConfig);
|
|
249
|
+
}
|
|
250
|
+
: undefined;
|
|
251
|
+
const credentials = ctx.credentials;
|
|
252
|
+
const readToken = credentials ? toolAccessToken(credentials, session.appName, session.userId, scope.signal) : undefined;
|
|
253
|
+
// A grant the user has not given (or that can no longer be renewed) is asked for, when the provider has a consent step.
|
|
254
|
+
const accessToken = readToken
|
|
255
|
+
? async (provider) => {
|
|
256
|
+
try {
|
|
257
|
+
return await readToken(provider);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
const ask = error instanceof ToolCredentialError && (error.code === 'not_connected' || error.code === 'expired' || error.code === 'refresh_failed');
|
|
261
|
+
if (ask && requestCredential && consent?.has(provider))
|
|
262
|
+
await requestCredential(provider);
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
: undefined;
|
|
267
|
+
return {
|
|
268
|
+
invocationId: ctx.invocationId,
|
|
269
|
+
agentName: agent.name,
|
|
270
|
+
functionCallId,
|
|
271
|
+
userId: session.userId,
|
|
272
|
+
appName: session.appName,
|
|
273
|
+
sessionId: session.id,
|
|
274
|
+
state,
|
|
275
|
+
get stateDelta() {
|
|
276
|
+
return delta;
|
|
277
|
+
},
|
|
278
|
+
actions,
|
|
279
|
+
// ADK's Context.requestConfirmation: the request, keyed by the call's id.
|
|
280
|
+
requestConfirmation: ({ hint, payload } = {}) => {
|
|
281
|
+
if (!functionCallId)
|
|
282
|
+
throw new Error('functionCallId is not set.');
|
|
283
|
+
setOwn(actions.requestedToolConfirmations, functionCallId, { hint: hint ?? '', confirmed: false, payload });
|
|
284
|
+
},
|
|
285
|
+
// The person's answer, when this call is the pinned call an approval resumes (interrupts.ts).
|
|
286
|
+
confirmation,
|
|
287
|
+
toolConfirmation: confirmation,
|
|
288
|
+
signal: scope.signal,
|
|
289
|
+
abortSignal: scope.signal,
|
|
290
|
+
...(ctx.userContent ? { userContent: ctx.userContent } : {}),
|
|
291
|
+
...(memory
|
|
292
|
+
? { searchMemory: (query) => memory.search({ appName: session.appName, userId: session.userId, query }) }
|
|
293
|
+
: {}),
|
|
294
|
+
...(accessToken ? { accessToken } : {}),
|
|
295
|
+
...(requestCredential ? { requestCredential } : {}),
|
|
296
|
+
invocationContext: {
|
|
297
|
+
invocationId: ctx.invocationId,
|
|
298
|
+
agent: { name: agent.name },
|
|
299
|
+
branch: ctx.branch,
|
|
300
|
+
session,
|
|
301
|
+
userContent: ctx.userContent,
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/** What a tool's result becomes when it nests past MAX_VALUE_DEPTH (ADR 0101). */
|
|
306
|
+
export const TOO_DEEP_RESULT = (toolName) => `The result of tool '${toolName}' nested deeper than ${MAX_VALUE_DEPTH} levels and was not kept.`;
|
|
307
|
+
/** ADK's normalization of a tool's result into a function response. */
|
|
308
|
+
function asResponse(value) {
|
|
309
|
+
if (typeof value !== 'object' || value === null)
|
|
310
|
+
return { result: value };
|
|
311
|
+
if (Array.isArray(value))
|
|
312
|
+
return { results: value };
|
|
313
|
+
return value;
|
|
314
|
+
}
|
|
315
|
+
function isAdkShaped(tool) {
|
|
316
|
+
return !!tool && typeof tool === 'object' && typeof tool.runAsync === 'function';
|
|
317
|
+
}
|
|
318
|
+
/** An own Tool's call, as FunctionTool runs it on the ADK path: its approval gate, then execute, a throw named for the tool. */
|
|
319
|
+
async function runOwnTool(tool, args, context) {
|
|
320
|
+
try {
|
|
321
|
+
if (tool.requiresApproval === true) {
|
|
322
|
+
if (!context.confirmation) {
|
|
323
|
+
context.requestConfirmation({ hint: APPROVAL_TEXTS.hint(tool.name) });
|
|
324
|
+
context.actions.skipSummarization = true;
|
|
325
|
+
return { error: APPROVAL_TEXTS.pending };
|
|
326
|
+
}
|
|
327
|
+
if (!context.confirmation.confirmed)
|
|
328
|
+
return { error: APPROVAL_TEXTS.rejected };
|
|
329
|
+
}
|
|
330
|
+
return await tool.execute(args, context);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
334
|
+
throw new Error(`Error in tool '${tool.name}': ${message}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function runCall(scope, call, tools, correction, confirmation) {
|
|
338
|
+
const context = callContext(scope, call.id || undefined, confirmation);
|
|
339
|
+
const name = call.name ?? '';
|
|
340
|
+
const tool = name && tools.has(name) ? tools.get(name) : undefined;
|
|
341
|
+
const callable = isTool(tool) || isAdkShaped(tool);
|
|
342
|
+
if (!callable) {
|
|
343
|
+
const toolName = name || '<unnamed>';
|
|
344
|
+
const notFound = `Function ${toolName} is not found in the toolsDict.`;
|
|
345
|
+
const guided = await correction?.failed(toolName, call.args ?? {}, new Error(notFound));
|
|
346
|
+
const part = {
|
|
347
|
+
functionResponse: { name: toolName, response: guided ?? { error: notFound }, id: context.functionCallId },
|
|
348
|
+
};
|
|
349
|
+
return { part, actions: context.actions };
|
|
350
|
+
}
|
|
351
|
+
const args = call.args ?? {};
|
|
352
|
+
const toolName = tool.name;
|
|
353
|
+
const longRunning = isTool(tool) ? tool.longRunning === true : tool.isLongRunning === true;
|
|
354
|
+
// Delegation (WS2-6): a subagent runs as its own child loop, a nested workflow as its own walk (ADR 0098), before the generic path (delegate.ts).
|
|
355
|
+
const subagent = subagentOf(tool);
|
|
356
|
+
const workflow = subagent ? undefined : workflowSubagentOf(tool);
|
|
357
|
+
const delegation = { ctx: scope.ctx, stateBase: scope.stateBase, signal: scope.signal, runLoop: runAgentLoop, queue: scope };
|
|
358
|
+
let response;
|
|
359
|
+
let failure;
|
|
360
|
+
try {
|
|
361
|
+
response = subagent
|
|
362
|
+
? await runSubagent(subagent, args, context, delegation)
|
|
363
|
+
: workflow
|
|
364
|
+
? await runWorkflowSubagent(workflow, args, context, delegation)
|
|
365
|
+
: isTool(tool) ? await runOwnTool(tool, args, context) : await tool.runAsync({ args, toolContext: context });
|
|
366
|
+
}
|
|
367
|
+
catch (e) {
|
|
368
|
+
failure = e instanceof Error ? e.message : e;
|
|
369
|
+
// Self-correction answers a thrown Error with reflection guidance in its place; a call waiting on a grant is not a failure.
|
|
370
|
+
const guided = isEmptyRecord(context.actions.requestedAuthConfigs) ? await correction?.failed(toolName, args, e) : undefined;
|
|
371
|
+
if (guided)
|
|
372
|
+
[response, failure] = [guided, undefined];
|
|
373
|
+
}
|
|
374
|
+
// Consent hook (WS6-3b, ADR 0085): a call that asked for a grant answers that it waits, whatever it returned or threw.
|
|
375
|
+
const asked = Object.values(context.actions.requestedAuthConfigs ?? {});
|
|
376
|
+
if (asked.length > 0) {
|
|
377
|
+
const provider = String(asked[0]?.credentialKey ?? '');
|
|
378
|
+
return { part: { functionResponse: { id: context.functionCallId, name: toolName, response: { result: CONSENT_TEXTS.pending(provider) } } }, actions: context.actions };
|
|
379
|
+
}
|
|
380
|
+
if (failure === undefined)
|
|
381
|
+
await correction?.answered(toolName, response);
|
|
382
|
+
// As ADK: a long-running call with no response answers nothing, even when it threw.
|
|
383
|
+
if (longRunning && (response === null || response === undefined)) {
|
|
384
|
+
return isDefaultActions(context.actions) ? undefined : { actions: context.actions };
|
|
385
|
+
}
|
|
386
|
+
const answer = failure ? { error: failure } : response === null || response === undefined ? { result: response } : asResponse(response);
|
|
387
|
+
// A result nested past MAX_VALUE_DEPTH would overflow every later reader of the session (lib/runtime/valueDepth.ts, ADR 0101): a note stands in for it.
|
|
388
|
+
const kept = nestedDeeperThan(answer) ? { error: TOO_DEEP_RESULT(toolName) } : answer;
|
|
389
|
+
return { part: { functionResponse: { id: context.functionCallId, name: toolName, response: kept } }, actions: context.actions };
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* The calls run, in parallel, as the one response event ADK stores for them
|
|
393
|
+
* (not yet stored); undefined when none answered. `confirmations` holds the
|
|
394
|
+
* person's answer for a pinned call an approval resumes, by call id.
|
|
395
|
+
*/
|
|
396
|
+
async function runCalls(scope, calls, tools, confirmations) {
|
|
397
|
+
const order = scope.selfCorrection?.forCalls(scope.ctx.invocationId, calls.length);
|
|
398
|
+
const outcomes = (await Promise.all(calls.map((call, i) => traceToolCall(call, tools.get(call.name ?? ''), () => runCall(scope, call, tools, order?.call(i), call.id ? confirmations?.get(call.id) : undefined)).finally(() => order?.release(i))))).filter((o) => !!o);
|
|
399
|
+
if (outcomes.length === 0)
|
|
400
|
+
return undefined;
|
|
401
|
+
const base = { invocationId: scope.ctx.invocationId, author: scope.agent.name };
|
|
402
|
+
const contentOf = (parts) => ({ role: 'user', parts });
|
|
403
|
+
if (outcomes.length === 1) {
|
|
404
|
+
const [only] = outcomes;
|
|
405
|
+
return createTurnEvent({
|
|
406
|
+
...base,
|
|
407
|
+
...(only.part ? { content: contentOf([only.part]) } : {}),
|
|
408
|
+
actions: only.actions,
|
|
409
|
+
branch: scope.ctx.branch,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
return createTurnEvent({
|
|
413
|
+
...base,
|
|
414
|
+
branch: scope.ctx.branch,
|
|
415
|
+
content: contentOf(outcomes.flatMap((o) => (o.part ? [o.part] : []))),
|
|
416
|
+
actions: mergeActions(outcomes.map((o) => o.actions)),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/** ADK's generateAuthEvent: a credential request per call that asked for one. */
|
|
420
|
+
function authEvent(scope, response) {
|
|
421
|
+
const configs = response.actions.requestedAuthConfigs;
|
|
422
|
+
if (isEmptyRecord(configs))
|
|
423
|
+
return undefined;
|
|
424
|
+
const parts = Object.entries(configs).map(([callId, authConfig]) => ({
|
|
425
|
+
functionCall: { name: REQUEST_CREDENTIAL, args: { function_call_id: callId, auth_config: authConfig }, id: `${ADK_CALL_ID_PREFIX}${randomUUID()}` },
|
|
426
|
+
}));
|
|
427
|
+
return createTurnEvent({
|
|
428
|
+
invocationId: scope.ctx.invocationId,
|
|
429
|
+
author: scope.agent.name,
|
|
430
|
+
branch: scope.ctx.branch,
|
|
431
|
+
content: { parts, role: response.content?.role ?? 'user' },
|
|
432
|
+
longRunningToolIds: parts.map((p) => p.functionCall?.id),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/** ADK's generateRequestConfirmationEvent: an adk_request_confirmation call per gated call, carrying the response's actions. */
|
|
436
|
+
function confirmationEvent(scope, modelEvent, response) {
|
|
437
|
+
const requested = response.actions.requestedToolConfirmations;
|
|
438
|
+
if (isEmptyRecord(requested))
|
|
439
|
+
return undefined;
|
|
440
|
+
const calls = getFunctionCalls(modelEvent);
|
|
441
|
+
const parts = [];
|
|
442
|
+
for (const [callId, toolConfirmation] of Object.entries(requested)) {
|
|
443
|
+
const originalFunctionCall = calls.find((c) => c.id === callId);
|
|
444
|
+
if (!originalFunctionCall)
|
|
445
|
+
continue;
|
|
446
|
+
parts.push({ functionCall: { name: REQUEST_CONFIRMATION, args: { originalFunctionCall, toolConfirmation }, id: `${ADK_CALL_ID_PREFIX}${randomUUID()}` } });
|
|
447
|
+
}
|
|
448
|
+
return createTurnEvent({
|
|
449
|
+
invocationId: scope.ctx.invocationId,
|
|
450
|
+
author: scope.agent.name,
|
|
451
|
+
branch: scope.ctx.branch,
|
|
452
|
+
content: { parts, role: response.content?.role ?? 'user' },
|
|
453
|
+
actions: response.actions,
|
|
454
|
+
longRunningToolIds: parts.map((p) => p.functionCall?.id),
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* ADK's request-confirmation processor, before a step: the pinned calls the
|
|
459
|
+
* latest user message approves or refuses run with the answer in their
|
|
460
|
+
* context, and their response is returned to be stored (interrupts.ts). Undefined when
|
|
461
|
+
* there is none; 'stopped' when the turn stopped while they ran (nothing is
|
|
462
|
+
* stored). Throws IntentMismatchError when an answer does not bind.
|
|
463
|
+
*/
|
|
464
|
+
async function resumeApprovals(agent, ctx, stateBase, selfCorrection) {
|
|
465
|
+
const signal = eitherSignal(ctx.signal, currentTurnSignal());
|
|
466
|
+
const scope = { agent, ctx, stateBase, selfCorrection, ...(signal ? { signal } : {}) };
|
|
467
|
+
const approved = await approvedCalls(agent, ctx, (id) => callContext(scope, id));
|
|
468
|
+
if (!approved)
|
|
469
|
+
return undefined;
|
|
470
|
+
const response = await runCalls(scope, approved.calls, approved.tools, approved.confirmations);
|
|
471
|
+
if (signal?.aborted)
|
|
472
|
+
return 'stopped';
|
|
473
|
+
return response;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* ADK's auth preprocessor, before a step (ADR 0085): the paused calls whose
|
|
477
|
+
* credential requests the latest user message answers run again, now with
|
|
478
|
+
* the grant in the store (interrupts.ts grantedCalls). Returns their
|
|
479
|
+
* response and the credential request a call raised again, if any, to be
|
|
480
|
+
* stored; undefined when there is none; 'stopped' when the turn stopped
|
|
481
|
+
* while they ran.
|
|
482
|
+
*/
|
|
483
|
+
async function resumeGrants(agent, ctx, stateBase, selfCorrection) {
|
|
484
|
+
const granted = await grantedCalls(agent, ctx);
|
|
485
|
+
if (!granted)
|
|
486
|
+
return undefined;
|
|
487
|
+
const signal = eitherSignal(ctx.signal, currentTurnSignal());
|
|
488
|
+
const scope = { agent, ctx, stateBase, selfCorrection, ...(signal ? { signal } : {}) };
|
|
489
|
+
const response = await runCalls(scope, granted.calls, granted.tools);
|
|
490
|
+
if (signal?.aborted)
|
|
491
|
+
return 'stopped';
|
|
492
|
+
if (!response)
|
|
493
|
+
return undefined;
|
|
494
|
+
const auth = authEvent(scope, response);
|
|
495
|
+
return { response, ...(auth ? { auth } : {}) };
|
|
496
|
+
}
|
|
497
|
+
// ── The model step, with partials and the fallback ───────────────────────────
|
|
498
|
+
/** Runs a step, yielding its partial events as they arrive; returns the step's result. */
|
|
499
|
+
async function* withPartials(start) {
|
|
500
|
+
const queue = [];
|
|
501
|
+
let wake;
|
|
502
|
+
let done = false;
|
|
503
|
+
const settle = () => {
|
|
504
|
+
done = true;
|
|
505
|
+
wake?.();
|
|
506
|
+
};
|
|
507
|
+
const run = start((event) => {
|
|
508
|
+
queue.push(event);
|
|
509
|
+
wake?.();
|
|
510
|
+
});
|
|
511
|
+
run.then(settle, settle);
|
|
512
|
+
for (;;) {
|
|
513
|
+
while (queue.length > 0)
|
|
514
|
+
yield queue.shift();
|
|
515
|
+
if (done)
|
|
516
|
+
break;
|
|
517
|
+
await new Promise((resolve) => (wake = resolve));
|
|
518
|
+
wake = undefined;
|
|
519
|
+
}
|
|
520
|
+
return await run;
|
|
521
|
+
}
|
|
522
|
+
const hasParts = (event) => (event.content?.parts?.length ?? 0) > 0;
|
|
523
|
+
/**
|
|
524
|
+
* One model step for the agent: its own model, and with a fallback_model
|
|
525
|
+
* the fallback by ADK's FallbackLlm's rules (ADR 0044), each a leaf adapter
|
|
526
|
+
* called by its own step (ADR 0053).
|
|
527
|
+
*/
|
|
528
|
+
async function* modelStep(agent, ctx, stepOptions) {
|
|
529
|
+
const adapterFor = ctx.adapterFor ?? resolveAdapter;
|
|
530
|
+
const fallbackId = agent.fallbackModel;
|
|
531
|
+
if (!fallbackId) {
|
|
532
|
+
return yield* withPartials((onPartial) => runModelStep({ ...stepOptions, adapter: adapterFor(agent.model), onPartial }));
|
|
533
|
+
}
|
|
534
|
+
const log = ctx.log ?? ((m) => console.warn(m));
|
|
535
|
+
const onFallback = (why) => {
|
|
536
|
+
log(`[models] ${why}; answering from ${fallbackId}.`);
|
|
537
|
+
return withPartials((onPartial) => runModelStep({ ...stepOptions, adapter: adapterFor(fallbackId), model: fallbackId, onPartial }));
|
|
538
|
+
};
|
|
539
|
+
const provider = providerForModel(agent.model);
|
|
540
|
+
if (circuitOpen(provider))
|
|
541
|
+
return yield* onFallback(`${provider} circuit is open`);
|
|
542
|
+
let answered = false;
|
|
543
|
+
const first = yield* withPartials((onPartial) => runModelStep({
|
|
544
|
+
...stepOptions,
|
|
545
|
+
adapter: adapterFor(agent.model),
|
|
546
|
+
onPartial: (event) => {
|
|
547
|
+
if (hasParts(event))
|
|
548
|
+
answered = true;
|
|
549
|
+
onPartial(event);
|
|
550
|
+
},
|
|
551
|
+
// FallbackLlm: a retryable failure counts against the provider, and redirects when nothing was produced.
|
|
552
|
+
redirect: (final, produced) => {
|
|
553
|
+
if (!final.error?.retryable)
|
|
554
|
+
return false;
|
|
555
|
+
recordFailure(provider);
|
|
556
|
+
return !produced && final.parts.length === 0;
|
|
557
|
+
},
|
|
558
|
+
}));
|
|
559
|
+
if (first.redirected) {
|
|
560
|
+
return yield* onFallback(`${agent.model} failed (${first.redirected.code}: ${first.redirected.message.slice(0, 120)})`);
|
|
561
|
+
}
|
|
562
|
+
if (first.event && hasParts(first.event))
|
|
563
|
+
answered = true;
|
|
564
|
+
if (answered && !first.error && !first.stopped)
|
|
565
|
+
recordSuccess(provider);
|
|
566
|
+
return first;
|
|
567
|
+
}
|
|
568
|
+
// ── The loop ─────────────────────────────────────────────────────────────────
|
|
569
|
+
/**
|
|
570
|
+
* Runs `agent` on the session in `ctx` until its answer is final, yielding
|
|
571
|
+
* every event in order: partials as they arrive (never stored), then each
|
|
572
|
+
* event as the store returned it. Returns how the run ended.
|
|
573
|
+
*
|
|
574
|
+
* Never throws for a failed model call or a failing tool: the first is the
|
|
575
|
+
* last event, stored with its error, the second the call's error response.
|
|
576
|
+
* An adapter that throws an Error ends the step on ADK's error event for
|
|
577
|
+
* it (lib/runtime/native/step.ts). Throws where the ADK runtime throws: a
|
|
578
|
+
* request that cannot be built, a store that throws.
|
|
579
|
+
*/
|
|
580
|
+
export function runAgentLoop(agent, ctx) {
|
|
581
|
+
// Telemetry hook (WS2-11, lib/runtime/native/telemetry.ts): the run is an agent.invoke span.
|
|
582
|
+
return traceAgentInvocation(agent, ctx, () => agentLoop(agent, ctx));
|
|
583
|
+
}
|
|
584
|
+
async function* agentLoop(agent, ctx) {
|
|
585
|
+
const { session, sessions } = ctx;
|
|
586
|
+
// The run's temp: keys, read from each event before the store drops them (lib/runtime/native/tempState.ts).
|
|
587
|
+
const runTemp = createRunTempState();
|
|
588
|
+
// Task-mode hook (WS3-5): a task node's run ends on finish_task's successful answer.
|
|
589
|
+
const task = ctx.taskNode && agent.mode === 'task' ? taskNodeRun(agent) : undefined;
|
|
590
|
+
const beforeStore = (event) => {
|
|
591
|
+
saveOutput(agent, event);
|
|
592
|
+
task?.beforeStore(event);
|
|
593
|
+
ctx.nodeStamp?.(event);
|
|
594
|
+
runTemp.record(event);
|
|
595
|
+
};
|
|
596
|
+
const store = async (event) => {
|
|
597
|
+
beforeStore(event);
|
|
598
|
+
return sessions.append(session, event);
|
|
599
|
+
};
|
|
600
|
+
let steps = 0;
|
|
601
|
+
let lastEvent;
|
|
602
|
+
const selfCorrection = ctx.selfCorrection ?? new SelfCorrection();
|
|
603
|
+
const correction = selfCorrection.forModel(agent.name, ctx.invocationId);
|
|
604
|
+
for (;;) {
|
|
605
|
+
// Consent hook (WS6-3b, interrupts.ts): a granted credential request runs its paused call again before the step, as ADK's auth preprocessor
|
|
606
|
+
// does, ahead of the approval resume as ADK orders its processors. A call that asks again pauses the run on its new request.
|
|
607
|
+
const regranted = await resumeGrants(agent, ctx, withStateOverlay(session.state, runTemp.values()), selfCorrection);
|
|
608
|
+
if (regranted === 'stopped')
|
|
609
|
+
return { reason: 'stopped', steps, lastEvent, stop: stopOf() };
|
|
610
|
+
if (regranted) {
|
|
611
|
+
if (regranted.auth) {
|
|
612
|
+
lastEvent = await store(regranted.auth);
|
|
613
|
+
yield lastEvent;
|
|
614
|
+
}
|
|
615
|
+
const pausedOn = regranted.auth ? [...(lastEvent?.longRunningToolIds ?? [])] : [];
|
|
616
|
+
lastEvent = await store(regranted.response);
|
|
617
|
+
yield lastEvent;
|
|
618
|
+
if (pausedOn.length > 0)
|
|
619
|
+
return { reason: 'paused', steps, lastEvent, pending: pausedOn };
|
|
620
|
+
}
|
|
621
|
+
// Interrupts hook (WS2-7a, interrupts.ts): an answered approval runs its pinned call before the step, as ADK's request-confirmation processor does; it runs
|
|
622
|
+
// first, then compaction (ADK inserts its compactor before the contents processor), both before the step budget. ADK's request-input processor sits
|
|
623
|
+
// between them and resumes node-tool calls only, which no native agent lists; an answered ask_user needs none (interrupts.ts, ADR 0079).
|
|
624
|
+
const resumed = await resumeApprovals(agent, ctx, withStateOverlay(session.state, runTemp.values()), selfCorrection);
|
|
625
|
+
if (resumed === 'stopped')
|
|
626
|
+
return { reason: 'stopped', steps, lastEvent, stop: stopOf() };
|
|
627
|
+
if (resumed) {
|
|
628
|
+
lastEvent = await store(resumed);
|
|
629
|
+
yield lastEvent;
|
|
630
|
+
}
|
|
631
|
+
// Compaction hook (WS2-9, lib/runtime/native/compaction.ts): with `context:`, the summary is stored before the step reads the history,
|
|
632
|
+
// before the step budget, as ADK's request processors run before its call count.
|
|
633
|
+
const compacted = await compactBeforeStep(agent, {
|
|
634
|
+
session,
|
|
635
|
+
adapterFor: ctx.adapterFor ?? resolveAdapter,
|
|
636
|
+
...(ctx.branch !== undefined ? { branch: ctx.branch } : {}),
|
|
637
|
+
...(ctx.isolationScope !== undefined ? { isolationScope: ctx.isolationScope } : {}),
|
|
638
|
+
...(currentTurnSignal() ? { signal: currentTurnSignal() } : {}),
|
|
639
|
+
});
|
|
640
|
+
if (compacted) {
|
|
641
|
+
// As ADK's Runner: a turn that stopped during the summary stores nothing and makes no step.
|
|
642
|
+
if (ctx.signal?.aborted || currentTurnSignal()?.aborted)
|
|
643
|
+
return { reason: 'stopped', steps, lastEvent, stop: stopOf() };
|
|
644
|
+
// A workflow node's stamp (ADR 0093): ADK's node runner stamps the summary as it does every event its agent yields, and outside
|
|
645
|
+
// task mode its maybeSetOutput gives it the summary as output. The outputKey and task hooks never see it, as on ADK.
|
|
646
|
+
ctx.nodeStamp?.(compacted);
|
|
647
|
+
yield await sessions.append(session, compacted);
|
|
648
|
+
}
|
|
649
|
+
if (steps >= MAX_LLM_CALLS) {
|
|
650
|
+
return { reason: 'stopped', steps, lastEvent, stop: { code: 'STEP_LIMIT', message: `Max number of llm calls limit of ${MAX_LLM_CALLS} exceeded` } };
|
|
651
|
+
}
|
|
652
|
+
steps += 1;
|
|
653
|
+
// Telemetry hook: each step is a model.call span.
|
|
654
|
+
const step = yield* traceModelCall(agent, ctx, (traced) => modelStep(agent, traced, { ...traced, agent, beforeAppend: beforeStore, stateOverlay: runTemp.values(), ...(correction ? { correction } : {}) }));
|
|
655
|
+
if (step.stopped)
|
|
656
|
+
return { reason: 'stopped', steps, lastEvent, stop: step.stopped };
|
|
657
|
+
const modelEvent = step.event;
|
|
658
|
+
if (!modelEvent)
|
|
659
|
+
return { reason: 'empty', steps, lastEvent };
|
|
660
|
+
yield modelEvent;
|
|
661
|
+
lastEvent = modelEvent;
|
|
662
|
+
let stepEnd = modelEvent;
|
|
663
|
+
const hadCalls = getFunctionCalls(modelEvent).length > 0;
|
|
664
|
+
if (hadCalls) {
|
|
665
|
+
const scope = { agent, ctx, stateBase: withStateOverlay(session.state, runTemp.values()), selfCorrection, ...(step.request.signal ? { signal: step.request.signal } : {}) };
|
|
666
|
+
const response = await runCalls(scope, getFunctionCalls(modelEvent), step.tools);
|
|
667
|
+
// As ADK: a turn that stopped while the calls ran (a long subagent run, say) stores no response.
|
|
668
|
+
if (scope.signal?.aborted)
|
|
669
|
+
return { reason: 'stopped', steps, lastEvent, stop: stopOf() };
|
|
670
|
+
if (response) {
|
|
671
|
+
const auth = authEvent(scope, response);
|
|
672
|
+
if (auth) {
|
|
673
|
+
lastEvent = await store(auth);
|
|
674
|
+
yield lastEvent;
|
|
675
|
+
}
|
|
676
|
+
const authIds = auth ? [...(lastEvent.longRunningToolIds ?? [])] : [];
|
|
677
|
+
const confirmation = confirmationEvent(scope, modelEvent, response);
|
|
678
|
+
if (confirmation) {
|
|
679
|
+
lastEvent = await store(confirmation);
|
|
680
|
+
yield lastEvent;
|
|
681
|
+
return { reason: 'paused', steps, lastEvent, pending: [...(lastEvent.longRunningToolIds ?? [])] };
|
|
682
|
+
}
|
|
683
|
+
lastEvent = await store(response);
|
|
684
|
+
yield lastEvent;
|
|
685
|
+
// As ADK: the response asking for auth is final, and the run waits on its credential request (ADR 0085).
|
|
686
|
+
if (authIds.length > 0)
|
|
687
|
+
return { reason: 'paused', steps, lastEvent, pending: authIds };
|
|
688
|
+
stepEnd = lastEvent;
|
|
689
|
+
if (task?.finished)
|
|
690
|
+
return { reason: 'final', steps, lastEvent, output: task.output };
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
// ADK's runAsyncImpl: stop on a final event, unless it is an empty metadata event after tool calls.
|
|
694
|
+
const emptyMetadata = stepEnd.author === agent.name && !stepEnd.partial && !hasParts(stepEnd) && isDefaultActions(stepEnd.actions);
|
|
695
|
+
if (isFinal(stepEnd) && !(emptyMetadata && hadCalls)) {
|
|
696
|
+
if (stepEnd.errorCode && stepEnd.errorCode !== 'STOP')
|
|
697
|
+
return { reason: 'error', steps, lastEvent };
|
|
698
|
+
const answered = new Set(getFunctionResponses(stepEnd).map((r) => r.id));
|
|
699
|
+
const pending = (modelEvent.longRunningToolIds ?? []).filter((id) => !answered.has(id));
|
|
700
|
+
if (pending.length > 0)
|
|
701
|
+
return { reason: 'paused', steps, lastEvent, pending };
|
|
702
|
+
return { reason: 'final', steps, lastEvent };
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|