@agent-native/core 0.131.4 → 0.131.6
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/corpus/README.md +3 -3
- package/corpus/core/CHANGELOG.md +69 -0
- package/corpus/core/docs/content/agent-native-toolkit.mdx +13 -6
- package/corpus/core/docs/content/cloneable-saas.mdx +5 -5
- package/corpus/core/docs/content/creating-templates.mdx +9 -0
- package/corpus/core/docs/content/deployment.mdx +14 -10
- package/corpus/core/docs/content/docs-components.mdx +491 -0
- package/corpus/core/docs/content/doctor.mdx +8 -1
- package/corpus/core/docs/content/drop-in-agent.mdx +1 -1
- package/corpus/core/docs/content/extensions.mdx +18 -18
- package/corpus/core/docs/content/faq.mdx +4 -4
- package/corpus/core/docs/content/key-concepts.mdx +30 -21
- package/corpus/core/docs/content/template-brain-developers.mdx +4 -1
- package/corpus/core/docs/content/template-mail-developers.mdx +4 -1
- package/corpus/core/docs/content/what-is-agent-native.mdx +6 -6
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +449 -66
- package/corpus/core/src/a2a/correlation.ts +31 -0
- package/corpus/core/src/a2a/types.ts +4 -0
- package/corpus/core/src/action.ts +3 -0
- package/corpus/core/src/agent/engine/registry.ts +45 -5
- package/corpus/core/src/agent/production-agent.ts +151 -18
- package/corpus/core/src/agent/run-loop-with-resume.ts +190 -3
- package/corpus/core/src/agent/run-manager.ts +117 -41
- package/corpus/core/src/agent/thread-debug-history.ts +86 -0
- package/corpus/core/src/agent/types.ts +3 -1
- package/corpus/core/src/cli/create.ts +11 -1
- package/corpus/core/src/cli/templates-meta.ts +2 -2
- package/corpus/core/src/client/agent-chat-adapter.ts +91 -13
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +11 -9
- package/corpus/core/src/client/sse-event-processor.ts +17 -3
- package/corpus/core/src/db/client.ts +57 -17
- package/corpus/core/src/integrations/adapters/slack.ts +8 -3
- package/corpus/core/src/localization/default-messages.ts +44 -0
- package/corpus/core/src/observability/traces.ts +78 -5
- package/corpus/core/src/scripts/call-agent.ts +290 -9
- package/corpus/core/src/scripts/describe-workspace-apps.ts +2 -2
- package/corpus/core/src/secrets/crypto.ts +126 -34
- package/corpus/core/src/secrets/storage.ts +61 -25
- package/corpus/core/src/server/agent-capabilities.ts +1 -1
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +111 -7
- package/corpus/core/src/server/agent-chat/context-tools.ts +2 -0
- package/corpus/core/src/server/agent-chat/plugin-options.ts +34 -1
- package/corpus/core/src/server/agent-chat/prompt-resources.ts +1 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +91 -59
- package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +14 -7
- package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +14 -7
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
- package/corpus/core/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
- package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -7
- package/corpus/core/src/vite/client.ts +1 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-ops/SKILL.md +5 -4
- package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +6 -4
- package/corpus/templates/analytics/AGENTS.md +6 -4
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -3
- package/corpus/templates/analytics/changelog/2026-07-30-scheduled-dashboard-emails-complete-every-panel-without-pool.md +6 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +28 -1
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +18 -11
- package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +4 -4
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -5
- package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +6 -2
- package/corpus/templates/analytics/server/lib/dashboard-panel-source-resolver.ts +11 -4
- package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +49 -31
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -8
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +9 -3
- package/corpus/templates/analytics/server/lib/production-serverless-runtime.ts +11 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +3 -2
- package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +2 -1
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +2 -1
- package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -12
- package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +54 -6
- package/corpus/templates/brain/.agents/skills/brain/SKILL.md +20 -9
- package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +33 -1
- package/corpus/templates/brain/AGENTS.md +3 -1
- package/corpus/templates/brain/README.md +4 -1
- package/corpus/templates/brain/actions/_schemas.ts +30 -8
- package/corpus/templates/brain/actions/ask-brain.ts +124 -14
- package/corpus/templates/brain/actions/create-source.ts +18 -2
- package/corpus/templates/brain/actions/enqueue-captures-distillation.ts +8 -123
- package/corpus/templates/brain/actions/enqueue-distillation.ts +6 -127
- package/corpus/templates/brain/actions/import-capture.ts +17 -5
- package/corpus/templates/brain/actions/import-transcript.ts +17 -5
- package/corpus/templates/brain/actions/update-source.ts +25 -5
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/brain/changelog/2026-07-30-blessed-resources-can-feed-cited-company-answers.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-30-the-left-sidebar-no-longer-shows-a-blank-organization-placeh.md +6 -0
- package/corpus/templates/brain/server/lib/brain.ts +49 -19
- package/corpus/templates/brain/server/lib/distillation-queue.ts +147 -0
- package/corpus/templates/brain/server/lib/source-policy.ts +264 -0
- package/corpus/templates/brain/server/plugins/agent-chat.ts +3 -2
- package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +116 -19
- package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +51 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +3 -1
- package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +6 -0
- package/corpus/templates/content/.agents/skills/content-product-development/SKILL.md +47 -0
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +72 -8
- package/corpus/templates/content/changelog/2026-07-30-the-slash-command-hint-now-stays-on-only-the-active-empty-li.md +6 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +506 -11
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +148 -17
- package/corpus/templates/design/app/components/design/canvas-interactions/design-canvas-interactions.ts +357 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +7 -466
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +15 -311
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +19 -997
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +8 -1216
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +31 -38
- package/corpus/templates/design/package.json +0 -1
- package/corpus/templates/dispatch/AGENTS.md +8 -0
- package/corpus/templates/dispatch/DEVELOPING.md +29 -0
- package/corpus/templates/dispatch/actions/view-screen.ts +34 -2
- package/corpus/templates/dispatch/app/i18n-data.ts +457 -0
- package/corpus/templates/dispatch/changelog/2026-07-30-delegated-dispatch-work-continues-reliably-in-the-background.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-30-find-failed-runs-across-apps.md +6 -0
- package/corpus/templates/dispatch/netlify.toml +3 -0
- package/corpus/templates/slides/.agents/skills/analytics-data-for-decks/SKILL.md +8 -12
- package/corpus/templates/slides/AGENTS.md +4 -4
- package/corpus/templates/slides/actions/duplicate-deck.ts +19 -4
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +2 -10
- package/corpus/templates/slides/app/components/deck/MermaidRenderer.tsx +12 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +30 -13
- package/corpus/templates/slides/app/components/editor/DeckEditorSkeleton.tsx +35 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +63 -56
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +21 -76
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1110 -292
- package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +53 -0
- package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +235 -18
- package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +4 -1
- package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +11 -0
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +129 -0
- package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +70 -0
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +519 -65
- package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +36 -426
- package/corpus/templates/slides/app/components/visual-editor/DrawOverlay.tsx +19 -458
- package/corpus/templates/slides/app/context/DeckContext.tsx +179 -72
- package/corpus/templates/slides/app/global.css +105 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +8 -0
- package/corpus/templates/slides/app/lib/deck-editor-loading.ts +26 -0
- package/corpus/templates/slides/app/lib/mermaid-blocks.ts +27 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +71 -38
- package/corpus/templates/slides/app/pages/Index.tsx +98 -41
- package/corpus/templates/slides/changelog/2026-07-30-deck-loading-no-longer-flashes-an-unavailable-message.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-duplicating-a-deck-from-the-deck-list-now-opens-the-copy-imm.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-layout-overflow-warning-is-readable-and-dismissible.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-shared-canvas-annotations.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slide-objects-can-be-moved-as-a-group-copied-and-pasted-with.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slide-text-editing-and-object-controls-now-match-google-slides.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-style-panel-now-scrolls-with-speaker-notes-open-shows-the-sl.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-top-toolbar-controls-now-use-consistent-sizing-with-undo-and.md +6 -0
- package/corpus/templates/slides/netlify.toml +3 -1
- package/corpus/templates/slides/package.json +0 -1
- package/corpus/templates/slides/server/lib/chat-attachments.ts +18 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/slides/vite.config.ts +0 -1
- package/corpus/toolkit/CHANGELOG.md +13 -0
- package/corpus/toolkit/README.md +19 -0
- package/corpus/toolkit/agent-native.eject.json +38 -0
- package/corpus/toolkit/package.json +29 -1
- package/corpus/toolkit/src/canvas-annotations/CanvasCommentPins.tsx +861 -0
- package/corpus/toolkit/src/canvas-annotations/DrawOverlay.tsx +1233 -0
- package/corpus/toolkit/src/canvas-annotations/index.ts +15 -0
- package/corpus/toolkit/src/canvas-annotations/types.ts +14 -0
- package/corpus/toolkit/src/canvas-interactions/canvas-interactions.ts +933 -0
- package/corpus/toolkit/src/canvas-interactions/index.ts +67 -0
- package/corpus/toolkit/src/design-tweaks/scrub-input-utils.ts +311 -0
- package/corpus/toolkit/src/design-tweaks/scrub-input.tsx +491 -0
- package/corpus/toolkit/src/design-tweaks/visual-style-controls.tsx +26 -171
- package/corpus/toolkit/src/index.ts +1 -0
- package/dist/a2a/client.d.ts +23 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +309 -44
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/correlation.d.ts +1 -0
- package/dist/a2a/correlation.d.ts.map +1 -1
- package/dist/a2a/correlation.js +27 -0
- package/dist/a2a/correlation.js.map +1 -1
- package/dist/a2a/types.d.ts +4 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/action.d.ts +3 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +34 -7
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/production-agent.d.ts +40 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +113 -15
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +21 -0
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +163 -4
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/run-manager.d.ts +16 -9
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +93 -42
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/thread-debug-history.d.ts +10 -0
- package/dist/agent/thread-debug-history.d.ts.map +1 -0
- package/dist/agent/thread-debug-history.js +68 -0
- package/dist/agent/thread-debug-history.js.map +1 -0
- package/dist/agent/types.d.ts +3 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +6 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/templates-meta.js +2 -2
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +76 -13
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +1 -1
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +12 -3
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +48 -17
- package/dist/db/client.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/adapters/slack.js +7 -3
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/localization/default-messages.d.ts +43 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +43 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/traces.d.ts +3 -1
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +73 -4
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +233 -10
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/scripts/describe-workspace-apps.js +2 -2
- package/dist/scripts/describe-workspace-apps.js.map +1 -1
- package/dist/secrets/crypto.d.ts +31 -21
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +108 -33
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/storage.d.ts +3 -5
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +40 -25
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-capabilities.js +1 -1
- package/dist/server/agent-capabilities.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +17 -3
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +92 -3
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
- package/dist/server/agent-chat/context-tools.js +2 -0
- package/dist/server/agent-chat/context-tools.js.map +1 -1
- package/dist/server/agent-chat/plugin-options.d.ts +23 -0
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
- package/dist/server/agent-chat/plugin-options.js +4 -1
- package/dist/server/agent-chat/plugin-options.js.map +1 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-chat/prompt-resources.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +79 -56
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +14 -7
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +14 -7
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
- package/dist/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -7
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/agent-native-toolkit.mdx +13 -6
- package/docs/content/cloneable-saas.mdx +5 -5
- package/docs/content/creating-templates.mdx +9 -0
- package/docs/content/deployment.mdx +14 -10
- package/docs/content/docs-components.mdx +491 -0
- package/docs/content/doctor.mdx +8 -1
- package/docs/content/drop-in-agent.mdx +1 -1
- package/docs/content/extensions.mdx +18 -18
- package/docs/content/faq.mdx +4 -4
- package/docs/content/key-concepts.mdx +30 -21
- package/docs/content/template-brain-developers.mdx +4 -1
- package/docs/content/template-mail-developers.mdx +4 -1
- package/docs/content/what-is-agent-native.mdx +6 -6
- package/package.json +2 -2
- package/src/a2a/client.ts +449 -66
- package/src/a2a/correlation.ts +31 -0
- package/src/a2a/types.ts +4 -0
- package/src/action.ts +3 -0
- package/src/agent/engine/registry.ts +45 -5
- package/src/agent/production-agent.ts +151 -18
- package/src/agent/run-loop-with-resume.ts +190 -3
- package/src/agent/run-manager.ts +117 -41
- package/src/agent/thread-debug-history.ts +86 -0
- package/src/agent/types.ts +3 -1
- package/src/cli/create.ts +11 -1
- package/src/cli/templates-meta.ts +2 -2
- package/src/client/agent-chat-adapter.ts +91 -13
- package/src/client/resources/McpIntegrationDialog.tsx +11 -9
- package/src/client/sse-event-processor.ts +17 -3
- package/src/db/client.ts +57 -17
- package/src/integrations/adapters/slack.ts +8 -3
- package/src/localization/default-messages.ts +44 -0
- package/src/observability/traces.ts +78 -5
- package/src/scripts/call-agent.ts +290 -9
- package/src/scripts/describe-workspace-apps.ts +2 -2
- package/src/secrets/crypto.ts +126 -34
- package/src/secrets/storage.ts +61 -25
- package/src/server/agent-capabilities.ts +1 -1
- package/src/server/agent-chat/action-filters-a2a.ts +111 -7
- package/src/server/agent-chat/context-tools.ts +2 -0
- package/src/server/agent-chat/plugin-options.ts +34 -1
- package/src/server/agent-chat/prompt-resources.ts +1 -1
- package/src/server/agent-chat-plugin.ts +91 -59
- package/src/templates/default/.agents/skills/secrets/SKILL.md +14 -7
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +14 -7
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
- package/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -7
- package/src/vite/client.ts +1 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import * as jose from "jose";
|
|
2
4
|
|
|
3
5
|
import { ssrfSafeFetch } from "../extensions/url-safety.js";
|
|
@@ -59,6 +61,10 @@ import type {
|
|
|
59
61
|
} from "./types.js";
|
|
60
62
|
|
|
61
63
|
const DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS = 15_000;
|
|
64
|
+
const DEFAULT_A2A_DISCOVERY_TIMEOUT_MS = 3_000;
|
|
65
|
+
const MAX_A2A_RPC_ATTEMPTS = 3;
|
|
66
|
+
const A2A_RPC_RETRY_BASE_MS = 100;
|
|
67
|
+
export const MAX_A2A_CALLER_RESPONSE_CHARS = 32_768;
|
|
62
68
|
|
|
63
69
|
export class A2ATaskTimeoutError extends Error {
|
|
64
70
|
readonly taskId: string;
|
|
@@ -79,6 +85,54 @@ export class A2ATaskTimeoutError extends Error {
|
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
export type A2ATerminalTaskErrorState =
|
|
89
|
+
| "failed"
|
|
90
|
+
| "canceled"
|
|
91
|
+
| "input-required"
|
|
92
|
+
| "completed";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Preserves a receiver's terminal protocol state across the text-oriented
|
|
96
|
+
* callAgent convenience boundary. Callers can distinguish a real answer from
|
|
97
|
+
* failure, cancellation, approval/input, and an invalid empty completion
|
|
98
|
+
* without parsing English prose.
|
|
99
|
+
*/
|
|
100
|
+
export class A2ATaskTerminalError extends Error {
|
|
101
|
+
readonly taskId: string;
|
|
102
|
+
readonly state: A2ATerminalTaskErrorState;
|
|
103
|
+
readonly responseText: string;
|
|
104
|
+
readonly errorCode: string;
|
|
105
|
+
readonly task: Task;
|
|
106
|
+
|
|
107
|
+
constructor(
|
|
108
|
+
task: Task,
|
|
109
|
+
state: A2ATerminalTaskErrorState,
|
|
110
|
+
responseText: string,
|
|
111
|
+
errorCode = `a2a_task_${state.replace(/-/g, "_")}`,
|
|
112
|
+
) {
|
|
113
|
+
const detail = responseText.trim()
|
|
114
|
+
? `: ${boundA2ACallerResponseText(responseText).trim()}`
|
|
115
|
+
: "";
|
|
116
|
+
super(`A2A task ${task.id} ended ${state}${detail}`);
|
|
117
|
+
this.name = "A2ATaskTerminalError";
|
|
118
|
+
this.taskId = task.id;
|
|
119
|
+
this.state = state;
|
|
120
|
+
this.responseText = boundA2ACallerResponseText(responseText);
|
|
121
|
+
this.errorCode = errorCode;
|
|
122
|
+
this.task = task;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Keep both the answer lead and artifact/source tail within caller context. */
|
|
127
|
+
export function boundA2ACallerResponseText(value: string): string {
|
|
128
|
+
if (value.length <= MAX_A2A_CALLER_RESPONSE_CHARS) return value;
|
|
129
|
+
const marker =
|
|
130
|
+
"\n\n...[A2A response compacted at the caller boundary; use the receiving app or returned artifact links for full details]...\n\n";
|
|
131
|
+
const tailChars = 8_192;
|
|
132
|
+
const headChars = MAX_A2A_CALLER_RESPONSE_CHARS - tailChars - marker.length;
|
|
133
|
+
return value.slice(0, headChars) + marker + value.slice(-tailChars);
|
|
134
|
+
}
|
|
135
|
+
|
|
82
136
|
/**
|
|
83
137
|
* Sign a JWT for A2A cross-app identity verification.
|
|
84
138
|
*
|
|
@@ -217,7 +271,7 @@ export class A2AClient {
|
|
|
217
271
|
private async rpc(
|
|
218
272
|
method: string,
|
|
219
273
|
params: Record<string, unknown>,
|
|
220
|
-
options?: { requestTimeoutMs?: number },
|
|
274
|
+
options?: { requestTimeoutMs?: number; deadlineMs?: number },
|
|
221
275
|
): Promise<JsonRpcResponse> {
|
|
222
276
|
const body: JsonRpcRequest = {
|
|
223
277
|
jsonrpc: "2.0",
|
|
@@ -226,51 +280,114 @@ export class A2AClient {
|
|
|
226
280
|
params,
|
|
227
281
|
};
|
|
228
282
|
|
|
229
|
-
|
|
283
|
+
const discoveryTimeoutMs = resolveA2ADiscoveryTimeoutMs(
|
|
284
|
+
options?.requestTimeoutMs ?? this.requestTimeoutMs,
|
|
285
|
+
options?.deadlineMs,
|
|
286
|
+
);
|
|
287
|
+
await this.ensureEndpointCandidates(discoveryTimeoutMs);
|
|
230
288
|
let lastError: Error | null = null;
|
|
231
289
|
|
|
232
290
|
for (const url of this.endpointCandidates) {
|
|
233
291
|
for (let i = 0; i < this.apiKeyAttempts.length; i++) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
url,
|
|
238
|
-
body,
|
|
292
|
+
const maxAttempts = isRetrySafeA2ARpc(
|
|
293
|
+
method,
|
|
294
|
+
params,
|
|
239
295
|
this.apiKeyAttempts[i],
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
296
|
+
)
|
|
297
|
+
? MAX_A2A_RPC_ATTEMPTS
|
|
298
|
+
: 1;
|
|
299
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
300
|
+
const requestTimeoutMs = resolveA2ARequestTimeoutMs(
|
|
301
|
+
options?.requestTimeoutMs ?? this.requestTimeoutMs,
|
|
302
|
+
options?.deadlineMs,
|
|
303
|
+
);
|
|
304
|
+
if (requestTimeoutMs !== undefined && requestTimeoutMs <= 0) {
|
|
305
|
+
throw new Error("A2A request deadline exceeded");
|
|
306
|
+
}
|
|
307
|
+
console.log(
|
|
308
|
+
`[A2A Client] POST ${url} method=${method} attempt=${attempt}/${maxAttempts}`,
|
|
309
|
+
);
|
|
310
|
+
const startTime = Date.now();
|
|
311
|
+
let res: Response;
|
|
312
|
+
try {
|
|
313
|
+
res = await this.postJson(
|
|
314
|
+
url,
|
|
315
|
+
body,
|
|
316
|
+
this.apiKeyAttempts[i],
|
|
317
|
+
requestTimeoutMs,
|
|
318
|
+
);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
lastError =
|
|
321
|
+
error instanceof Error ? error : new Error(String(error));
|
|
322
|
+
if (attempt < maxAttempts) {
|
|
323
|
+
await waitForA2ARetry(
|
|
324
|
+
attempt,
|
|
325
|
+
null,
|
|
326
|
+
remainingA2ADeadlineMs(options?.deadlineMs),
|
|
327
|
+
);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
console.log(
|
|
333
|
+
`[A2A Client] Response: ${res.status} in ${Date.now() - startTime}ms`,
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
if (res.ok) {
|
|
337
|
+
const text = await res.text();
|
|
338
|
+
if (
|
|
339
|
+
i < this.apiKeyAttempts.length - 1 &&
|
|
340
|
+
isA2AAuthRejectionResponse(res.status, text)
|
|
341
|
+
) {
|
|
342
|
+
lastError = new Error(
|
|
343
|
+
`A2A request failed (${res.status}): ${text}`,
|
|
344
|
+
);
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
const parsed = JSON.parse(text) as JsonRpcResponse;
|
|
349
|
+
this.endpointCandidates = [url];
|
|
350
|
+
this.markApiKeySucceeded(this.apiKeyAttempts[i]);
|
|
351
|
+
return parsed;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
lastError = new Error(
|
|
354
|
+
`A2A response was not valid JSON: ${
|
|
355
|
+
error instanceof Error ? error.message : String(error)
|
|
356
|
+
}`,
|
|
357
|
+
);
|
|
358
|
+
if (attempt < maxAttempts) {
|
|
359
|
+
await waitForA2ARetry(
|
|
360
|
+
attempt,
|
|
361
|
+
null,
|
|
362
|
+
remainingA2ADeadlineMs(options?.deadlineMs),
|
|
363
|
+
);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
245
369
|
|
|
246
|
-
if (res.ok) {
|
|
247
370
|
const text = await res.text();
|
|
371
|
+
lastError = new Error(`A2A request failed (${res.status}): ${text}`);
|
|
248
372
|
if (
|
|
249
373
|
i < this.apiKeyAttempts.length - 1 &&
|
|
250
374
|
isA2AAuthRejectionResponse(res.status, text)
|
|
251
375
|
) {
|
|
252
|
-
|
|
253
|
-
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
if (isRetryableA2AStatus(res.status) && attempt < maxAttempts) {
|
|
379
|
+
await waitForA2ARetry(
|
|
380
|
+
attempt,
|
|
381
|
+
res.headers.get("retry-after"),
|
|
382
|
+
remainingA2ADeadlineMs(options?.deadlineMs),
|
|
254
383
|
);
|
|
255
384
|
continue;
|
|
256
385
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const text = await res.text();
|
|
263
|
-
lastError = new Error(`A2A request failed (${res.status}): ${text}`);
|
|
264
|
-
if (
|
|
265
|
-
i < this.apiKeyAttempts.length - 1 &&
|
|
266
|
-
isA2AAuthRejectionResponse(res.status, text)
|
|
267
|
-
) {
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
if (!shouldTryNextEndpoint(res.status)) {
|
|
271
|
-
throw lastError;
|
|
386
|
+
if (!shouldTryNextEndpoint(res.status)) {
|
|
387
|
+
throw lastError;
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
272
390
|
}
|
|
273
|
-
break;
|
|
274
391
|
}
|
|
275
392
|
}
|
|
276
393
|
|
|
@@ -312,6 +429,10 @@ export class A2AClient {
|
|
|
312
429
|
metadata?: Record<string, unknown>;
|
|
313
430
|
idempotencyKey?: string;
|
|
314
431
|
approvedActions?: A2AApprovedAction[];
|
|
432
|
+
/** Per-request transport cap, bounded by deadlineMs when both exist. */
|
|
433
|
+
requestTimeoutMs?: number;
|
|
434
|
+
/** Absolute end-to-end deadline shared with async polling. */
|
|
435
|
+
deadlineMs?: number;
|
|
315
436
|
/**
|
|
316
437
|
* If true, ask the server to return the task immediately in `working`
|
|
317
438
|
* state and process the handler in the background. The caller should
|
|
@@ -323,16 +444,25 @@ export class A2AClient {
|
|
|
323
444
|
async?: boolean;
|
|
324
445
|
},
|
|
325
446
|
): Promise<Task> {
|
|
326
|
-
const response = await this.rpc(
|
|
327
|
-
message,
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
447
|
+
const response = await this.rpc(
|
|
448
|
+
"message/send",
|
|
449
|
+
{
|
|
450
|
+
message,
|
|
451
|
+
contextId: opts?.contextId,
|
|
452
|
+
metadata: opts?.metadata,
|
|
453
|
+
...(opts?.idempotencyKey
|
|
454
|
+
? { idempotencyKey: opts.idempotencyKey }
|
|
455
|
+
: {}),
|
|
456
|
+
...(opts?.approvedActions?.length
|
|
457
|
+
? { approvedActions: opts.approvedActions }
|
|
458
|
+
: {}),
|
|
459
|
+
...(opts?.async ? { async: true } : {}),
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
requestTimeoutMs: opts?.requestTimeoutMs,
|
|
463
|
+
deadlineMs: opts?.deadlineMs,
|
|
464
|
+
},
|
|
465
|
+
);
|
|
336
466
|
|
|
337
467
|
if (response.error) {
|
|
338
468
|
throw new Error(
|
|
@@ -348,12 +478,15 @@ export class A2AClient {
|
|
|
348
478
|
*/
|
|
349
479
|
async getTask(
|
|
350
480
|
taskId: string,
|
|
351
|
-
opts?: { requestTimeoutMs?: number },
|
|
481
|
+
opts?: { requestTimeoutMs?: number; deadlineMs?: number },
|
|
352
482
|
): Promise<Task> {
|
|
353
483
|
const response = await this.rpc(
|
|
354
484
|
"tasks/get",
|
|
355
485
|
{ id: taskId },
|
|
356
|
-
{
|
|
486
|
+
{
|
|
487
|
+
requestTimeoutMs: opts?.requestTimeoutMs,
|
|
488
|
+
deadlineMs: opts?.deadlineMs,
|
|
489
|
+
},
|
|
357
490
|
);
|
|
358
491
|
if (response.error) {
|
|
359
492
|
throw new Error(
|
|
@@ -411,6 +544,8 @@ export class A2AClient {
|
|
|
411
544
|
onUpdate?: (task: Task) => void;
|
|
412
545
|
},
|
|
413
546
|
): Promise<Task> {
|
|
547
|
+
const timeoutMs = opts?.timeoutMs ?? 5 * 60_000;
|
|
548
|
+
const deadlineMs = Date.now() + timeoutMs;
|
|
414
549
|
const submitted = await this.send(message, {
|
|
415
550
|
contextId: opts?.contextId,
|
|
416
551
|
metadata: opts?.metadata,
|
|
@@ -419,9 +554,14 @@ export class A2AClient {
|
|
|
419
554
|
? { approvedActions: opts.approvedActions }
|
|
420
555
|
: {}),
|
|
421
556
|
async: true,
|
|
557
|
+
requestTimeoutMs: Math.min(
|
|
558
|
+
this.requestTimeoutMs ?? DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS,
|
|
559
|
+
Math.max(1, deadlineMs - Date.now()),
|
|
560
|
+
),
|
|
561
|
+
deadlineMs,
|
|
422
562
|
});
|
|
423
563
|
|
|
424
|
-
return this.pollTask(submitted, opts);
|
|
564
|
+
return this.pollTask(submitted, { ...opts, timeoutMs, deadlineMs });
|
|
425
565
|
}
|
|
426
566
|
|
|
427
567
|
/**
|
|
@@ -440,9 +580,17 @@ export class A2AClient {
|
|
|
440
580
|
onUpdate?: (task: Task) => void;
|
|
441
581
|
},
|
|
442
582
|
): Promise<Task> {
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
583
|
+
const timeoutMs = opts?.timeoutMs ?? 5 * 60_000;
|
|
584
|
+
const deadlineMs = Date.now() + timeoutMs;
|
|
585
|
+
const current = await this.getTask(taskId, {
|
|
586
|
+
requestTimeoutMs: Math.min(
|
|
587
|
+
this.requestTimeoutMs ?? DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS,
|
|
588
|
+
Math.max(1, deadlineMs - Date.now()),
|
|
589
|
+
),
|
|
590
|
+
deadlineMs,
|
|
591
|
+
});
|
|
592
|
+
safelyNotifyA2AUpdate(opts?.onUpdate, current);
|
|
593
|
+
return this.pollTask(current, { ...opts, timeoutMs, deadlineMs });
|
|
446
594
|
}
|
|
447
595
|
|
|
448
596
|
private async pollTask(
|
|
@@ -451,6 +599,7 @@ export class A2AClient {
|
|
|
451
599
|
timeoutMs?: number;
|
|
452
600
|
pollIntervalMs?: number;
|
|
453
601
|
onUpdate?: (task: Task) => void;
|
|
602
|
+
deadlineMs?: number;
|
|
454
603
|
},
|
|
455
604
|
): Promise<Task> {
|
|
456
605
|
const terminalStates = new Set([
|
|
@@ -463,7 +612,7 @@ export class A2AClient {
|
|
|
463
612
|
|
|
464
613
|
const timeoutMs = opts?.timeoutMs ?? 5 * 60_000;
|
|
465
614
|
const pollMs = opts?.pollIntervalMs ?? 2_000;
|
|
466
|
-
const deadline = Date.now() + timeoutMs;
|
|
615
|
+
const deadline = opts?.deadlineMs ?? Date.now() + timeoutMs;
|
|
467
616
|
|
|
468
617
|
let current = submitted;
|
|
469
618
|
while (Date.now() < deadline) {
|
|
@@ -477,11 +626,15 @@ export class A2AClient {
|
|
|
477
626
|
this.requestTimeoutMs ?? DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS,
|
|
478
627
|
remainingMs,
|
|
479
628
|
),
|
|
629
|
+
deadlineMs: deadline,
|
|
480
630
|
});
|
|
481
|
-
opts?.onUpdate
|
|
482
|
-
} catch {
|
|
483
|
-
//
|
|
484
|
-
|
|
631
|
+
safelyNotifyA2AUpdate(opts?.onUpdate, current);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
// Retry only transport/gateway interruptions. Authentication,
|
|
634
|
+
// task-not-found, invalid params, and other protocol failures are
|
|
635
|
+
// permanent for this poll and must surface immediately.
|
|
636
|
+
if (isRetryableA2APollError(error)) continue;
|
|
637
|
+
throw error;
|
|
485
638
|
}
|
|
486
639
|
if (terminalStates.has(current.status.state)) return current;
|
|
487
640
|
}
|
|
@@ -563,7 +716,7 @@ export class A2AClient {
|
|
|
563
716
|
}
|
|
564
717
|
}
|
|
565
718
|
|
|
566
|
-
private async ensureEndpointCandidates(): Promise<void> {
|
|
719
|
+
private async ensureEndpointCandidates(timeoutMs?: number): Promise<void> {
|
|
567
720
|
if (this.endpointResolved) return;
|
|
568
721
|
this.endpointResolved = true;
|
|
569
722
|
|
|
@@ -571,7 +724,7 @@ export class A2AClient {
|
|
|
571
724
|
addDefaultEndpointCandidates(candidates, this.baseUrl);
|
|
572
725
|
|
|
573
726
|
try {
|
|
574
|
-
const card = await this.getAgentCard();
|
|
727
|
+
const card = await this.getAgentCard({ timeoutMs });
|
|
575
728
|
const cardUrl = normalizeUrl(card.url, this.baseUrl);
|
|
576
729
|
if (cardUrl) {
|
|
577
730
|
const explicitEndpoint = splitExplicitA2AEndpoint(cardUrl);
|
|
@@ -669,6 +822,137 @@ function shouldTryNextEndpoint(status: number): boolean {
|
|
|
669
822
|
return status === 404 || status === 405;
|
|
670
823
|
}
|
|
671
824
|
|
|
825
|
+
function isRetrySafeA2ARpc(
|
|
826
|
+
method: string,
|
|
827
|
+
params: Record<string, unknown>,
|
|
828
|
+
apiKey: string | undefined,
|
|
829
|
+
): boolean {
|
|
830
|
+
if (method === "message/send") {
|
|
831
|
+
return (
|
|
832
|
+
params.async === true &&
|
|
833
|
+
typeof params.idempotencyKey === "string" &&
|
|
834
|
+
params.idempotencyKey.trim().length > 0 &&
|
|
835
|
+
hasJwtSubject(apiKey)
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
return method === "tasks/get" || method === "actions/invoke";
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function isRetryableA2AStatus(status: number): boolean {
|
|
842
|
+
return (
|
|
843
|
+
status === 408 ||
|
|
844
|
+
status === 425 ||
|
|
845
|
+
status === 429 ||
|
|
846
|
+
status === 500 ||
|
|
847
|
+
status === 502 ||
|
|
848
|
+
status === 503 ||
|
|
849
|
+
status === 504
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async function waitForA2ARetry(
|
|
854
|
+
attempt: number,
|
|
855
|
+
retryAfter: string | null = null,
|
|
856
|
+
maxWaitMs?: number,
|
|
857
|
+
): Promise<void> {
|
|
858
|
+
const retryAfterSeconds = retryAfter ? Number(retryAfter) : NaN;
|
|
859
|
+
const retryAfterMs = Number.isFinite(retryAfterSeconds)
|
|
860
|
+
? Math.max(0, Math.min(2_000, retryAfterSeconds * 1_000))
|
|
861
|
+
: 0;
|
|
862
|
+
const backoffMs = Math.max(
|
|
863
|
+
retryAfterMs,
|
|
864
|
+
Math.min(1_000, A2A_RPC_RETRY_BASE_MS * 2 ** (attempt - 1)),
|
|
865
|
+
);
|
|
866
|
+
const boundedWaitMs =
|
|
867
|
+
maxWaitMs === undefined ? backoffMs : Math.min(backoffMs, maxWaitMs);
|
|
868
|
+
if (boundedWaitMs <= 0) return;
|
|
869
|
+
await new Promise((resolve) => setTimeout(resolve, boundedWaitMs));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function remainingA2ADeadlineMs(
|
|
873
|
+
deadlineMs: number | undefined,
|
|
874
|
+
): number | undefined {
|
|
875
|
+
return deadlineMs === undefined
|
|
876
|
+
? undefined
|
|
877
|
+
: Math.max(0, deadlineMs - Date.now());
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function resolveA2ARequestTimeoutMs(
|
|
881
|
+
configuredMs: number | undefined,
|
|
882
|
+
deadlineMs: number | undefined,
|
|
883
|
+
): number | undefined {
|
|
884
|
+
const remainingMs = remainingA2ADeadlineMs(deadlineMs);
|
|
885
|
+
if (remainingMs === undefined) return configuredMs;
|
|
886
|
+
return configuredMs === undefined
|
|
887
|
+
? remainingMs
|
|
888
|
+
: Math.min(configuredMs, remainingMs);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Agent-card discovery is only a hint because first-party endpoints have
|
|
893
|
+
* conventional paths. Preserve most of a bounded call's deadline for the
|
|
894
|
+
* actual message instead of letting a cold or unavailable card consume it.
|
|
895
|
+
*/
|
|
896
|
+
function resolveA2ADiscoveryTimeoutMs(
|
|
897
|
+
configuredMs: number | undefined,
|
|
898
|
+
deadlineMs: number | undefined,
|
|
899
|
+
): number {
|
|
900
|
+
const transportBudgetMs = resolveA2ARequestTimeoutMs(
|
|
901
|
+
configuredMs,
|
|
902
|
+
deadlineMs,
|
|
903
|
+
);
|
|
904
|
+
const remainingMs = remainingA2ADeadlineMs(deadlineMs);
|
|
905
|
+
const deadlineShareMs =
|
|
906
|
+
remainingMs === undefined
|
|
907
|
+
? DEFAULT_A2A_DISCOVERY_TIMEOUT_MS
|
|
908
|
+
: Math.max(1, Math.floor(remainingMs / 4));
|
|
909
|
+
return Math.min(
|
|
910
|
+
DEFAULT_A2A_DISCOVERY_TIMEOUT_MS,
|
|
911
|
+
transportBudgetMs ?? DEFAULT_A2A_DISCOVERY_TIMEOUT_MS,
|
|
912
|
+
deadlineShareMs,
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function hasJwtSubject(apiKey: string | undefined): boolean {
|
|
917
|
+
if (!apiKey) return false;
|
|
918
|
+
const segments = apiKey.split(".");
|
|
919
|
+
if (segments.length !== 3) return false;
|
|
920
|
+
try {
|
|
921
|
+
const payload = JSON.parse(
|
|
922
|
+
Buffer.from(segments[1], "base64url").toString("utf8"),
|
|
923
|
+
) as Record<string, unknown>;
|
|
924
|
+
return typeof payload.sub === "string" && payload.sub.trim().length > 0;
|
|
925
|
+
} catch {
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function isRetryableA2APollError(error: unknown): boolean {
|
|
931
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
932
|
+
if (isA2AAuthRejection(error)) return false;
|
|
933
|
+
if (
|
|
934
|
+
/A2A error \(-32\d+\)|A2A request failed \((?:400|403|404|405|409|422)\)/i.test(
|
|
935
|
+
message,
|
|
936
|
+
)
|
|
937
|
+
) {
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
return /(?:A2A request failed \((?:408|425|429|5\d\d)\)|fetch failed|network|socket|ECONN|ETIMEDOUT|timeout|deadline|aborted|invalid JSON)/i.test(
|
|
941
|
+
message,
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function safelyNotifyA2AUpdate(
|
|
946
|
+
callback: ((task: Task) => void) | undefined,
|
|
947
|
+
task: Task,
|
|
948
|
+
): void {
|
|
949
|
+
try {
|
|
950
|
+
callback?.(task);
|
|
951
|
+
} catch {
|
|
952
|
+
// Presentation/progress callbacks cannot alter task polling.
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
672
956
|
function unique(values: string[]): string[] {
|
|
673
957
|
return Array.from(new Set(values));
|
|
674
958
|
}
|
|
@@ -771,6 +1055,11 @@ export async function callAgent(
|
|
|
771
1055
|
// detached promises that get killed on Netlify/Vercel. Callers that
|
|
772
1056
|
// explicitly want a single-shot blocking POST can pass `async: false`.
|
|
773
1057
|
const useAsync = opts?.async ?? true;
|
|
1058
|
+
// A stable per-call key makes retrying a submission safe even when this
|
|
1059
|
+
// invocation has no durable parent turn id. If the receiver committed the
|
|
1060
|
+
// task but the response was lost, the retry reuses the task.
|
|
1061
|
+
const effectiveIdempotencyKey =
|
|
1062
|
+
opts?.idempotencyKey ?? (opts?.taskId ? undefined : `auto:${randomUUID()}`);
|
|
774
1063
|
const message: Message = {
|
|
775
1064
|
role: "user",
|
|
776
1065
|
parts: [{ type: "text", text }],
|
|
@@ -798,7 +1087,7 @@ export async function callAgent(
|
|
|
798
1087
|
: await client.sendAndWait(message, {
|
|
799
1088
|
contextId: opts?.contextId,
|
|
800
1089
|
metadata,
|
|
801
|
-
idempotencyKey:
|
|
1090
|
+
idempotencyKey: effectiveIdempotencyKey,
|
|
802
1091
|
...(opts?.approvedActions?.length
|
|
803
1092
|
? { approvedActions: opts.approvedActions }
|
|
804
1093
|
: {}),
|
|
@@ -813,30 +1102,67 @@ export async function callAgent(
|
|
|
813
1102
|
task = await client.send(message, {
|
|
814
1103
|
contextId: opts?.contextId,
|
|
815
1104
|
metadata,
|
|
816
|
-
idempotencyKey:
|
|
1105
|
+
idempotencyKey: effectiveIdempotencyKey,
|
|
817
1106
|
...(opts?.approvedActions?.length
|
|
818
1107
|
? { approvedActions: opts.approvedActions }
|
|
819
1108
|
: {}),
|
|
820
1109
|
});
|
|
821
1110
|
}
|
|
822
1111
|
|
|
823
|
-
//
|
|
1112
|
+
// Preserve the receiver's typed terminal state. Failed, canceled, and
|
|
1113
|
+
// input-required tasks are not successful text answers, even when the
|
|
1114
|
+
// receiver attached a friendly explanatory message.
|
|
824
1115
|
const responseMessage = task.status.message;
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1116
|
+
const responseText = responseMessage
|
|
1117
|
+
? extractMessageText(responseMessage)
|
|
1118
|
+
: "";
|
|
1119
|
+
const state = task.status.state;
|
|
1120
|
+
if (
|
|
1121
|
+
state === "failed" ||
|
|
1122
|
+
state === "canceled" ||
|
|
1123
|
+
state === "input-required"
|
|
1124
|
+
) {
|
|
1125
|
+
throw new A2ATaskTerminalError(task, state, responseText);
|
|
830
1126
|
}
|
|
831
|
-
|
|
832
|
-
|
|
1127
|
+
if (state !== "completed") {
|
|
1128
|
+
throw new A2ATaskTerminalError(
|
|
1129
|
+
task,
|
|
1130
|
+
"failed",
|
|
1131
|
+
responseText || `Unexpected terminal state: ${state}`,
|
|
1132
|
+
"a2a_invalid_terminal_state",
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
if (responseText.trim()) {
|
|
1136
|
+
if (responseText.length > MAX_A2A_CALLER_RESPONSE_CHARS) {
|
|
1137
|
+
throw new A2ATaskTerminalError(
|
|
1138
|
+
task,
|
|
1139
|
+
"completed",
|
|
1140
|
+
responseText,
|
|
1141
|
+
"a2a_response_too_large",
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
return responseText;
|
|
1145
|
+
}
|
|
1146
|
+
const artifactSummary = verifiedArtifactSummary(task);
|
|
1147
|
+
if (artifactSummary) return artifactSummary;
|
|
1148
|
+
throw new A2ATaskTerminalError(
|
|
1149
|
+
task,
|
|
1150
|
+
"completed",
|
|
1151
|
+
"Agent completed without a response or verified artifact.",
|
|
1152
|
+
"empty_agent_response",
|
|
1153
|
+
);
|
|
833
1154
|
} catch (err) {
|
|
834
1155
|
if (
|
|
835
1156
|
opts?.returnRecoverableArtifactsOnTimeout !== false &&
|
|
836
1157
|
err instanceof A2ATaskTimeoutError
|
|
837
1158
|
) {
|
|
838
1159
|
const recoverableText = extractRecoverableArtifactText(err.lastTask);
|
|
839
|
-
if (
|
|
1160
|
+
if (
|
|
1161
|
+
recoverableText &&
|
|
1162
|
+
recoverableText.length <= MAX_A2A_CALLER_RESPONSE_CHARS
|
|
1163
|
+
) {
|
|
1164
|
+
return recoverableText;
|
|
1165
|
+
}
|
|
840
1166
|
}
|
|
841
1167
|
if (i < apiKeyAttempts.length - 1 && isA2AAuthRejection(err)) {
|
|
842
1168
|
lastAuthError = err;
|
|
@@ -970,8 +1296,65 @@ function extractRecoverableArtifactText(task: Task): string {
|
|
|
970
1296
|
}
|
|
971
1297
|
|
|
972
1298
|
function extractMessageText(message: Message): string {
|
|
973
|
-
return message.parts
|
|
1299
|
+
return (Array.isArray(message.parts) ? message.parts : [])
|
|
974
1300
|
.filter((p): p is { type: "text"; text: string } => p.type === "text")
|
|
975
1301
|
.map((p) => p.text)
|
|
976
1302
|
.join("\n");
|
|
977
1303
|
}
|
|
1304
|
+
|
|
1305
|
+
function verifiedArtifactSummary(task: Task): string {
|
|
1306
|
+
const artifacts = Array.isArray(task.artifacts) ? task.artifacts : [];
|
|
1307
|
+
if (artifacts.length === 0) return "";
|
|
1308
|
+
const summaries = artifacts
|
|
1309
|
+
.map((artifact, artifactIndex) => {
|
|
1310
|
+
const parts = Array.isArray(artifact?.parts) ? artifact.parts : [];
|
|
1311
|
+
const usableParts = parts
|
|
1312
|
+
.map((part) => summarizeVerifiedArtifactPart(part))
|
|
1313
|
+
.filter((value): value is string => !!value)
|
|
1314
|
+
.slice(0, 5);
|
|
1315
|
+
if (usableParts.length === 0) return null;
|
|
1316
|
+
const fallbackFileName = parts.find(
|
|
1317
|
+
(part) => part.type === "file" && part.file.name?.trim(),
|
|
1318
|
+
);
|
|
1319
|
+
const label =
|
|
1320
|
+
artifact?.name?.trim() ||
|
|
1321
|
+
(fallbackFileName?.type === "file"
|
|
1322
|
+
? fallbackFileName.file.name?.trim()
|
|
1323
|
+
: "") ||
|
|
1324
|
+
`Artifact ${artifactIndex + 1}`;
|
|
1325
|
+
return `- ${label}\n${usableParts.map((part) => ` - ${part}`).join("\n")}`;
|
|
1326
|
+
})
|
|
1327
|
+
.filter((value): value is string => !!value)
|
|
1328
|
+
.slice(0, 20);
|
|
1329
|
+
if (summaries.length === 0) return "";
|
|
1330
|
+
return boundA2ACallerResponseText(
|
|
1331
|
+
`The agent completed with ${summaries.length} verified artifact(s):\n` +
|
|
1332
|
+
summaries.join("\n"),
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function summarizeVerifiedArtifactPart(part: unknown): string | null {
|
|
1337
|
+
if (!part || typeof part !== "object") return null;
|
|
1338
|
+
const candidate = part as Record<string, unknown>;
|
|
1339
|
+
if (candidate.type === "text" && typeof candidate.text === "string") {
|
|
1340
|
+
const text = candidate.text.trim();
|
|
1341
|
+
return text ? boundA2ACallerResponseText(text).slice(0, 2_000) : null;
|
|
1342
|
+
}
|
|
1343
|
+
if (candidate.type === "file") {
|
|
1344
|
+
const file = candidate.file as Record<string, unknown> | undefined;
|
|
1345
|
+
if (!file) return null;
|
|
1346
|
+
const uri = typeof file.uri === "string" ? file.uri.trim() : "";
|
|
1347
|
+
const name = typeof file.name === "string" ? file.name.trim() : "";
|
|
1348
|
+
const bytes = typeof file.bytes === "string" ? file.bytes : "";
|
|
1349
|
+
if (uri) return name ? `${name}: ${uri}` : uri;
|
|
1350
|
+
if (bytes) return name ? `${name} (inline file)` : "Inline file artifact";
|
|
1351
|
+
return null;
|
|
1352
|
+
}
|
|
1353
|
+
if (candidate.type === "data") {
|
|
1354
|
+
const data = candidate.data;
|
|
1355
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
1356
|
+
const serialized = JSON.stringify(data);
|
|
1357
|
+
return serialized !== "{}" ? serialized.slice(0, 2_000) : null;
|
|
1358
|
+
}
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|