@a5c-ai/kradle 5.0.1-staging.3abdf9534c25
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/Dockerfile +31 -0
- package/README.md +187 -0
- package/bin/kradle-demo.mjs +23 -0
- package/bin/kradle-server.mjs +14 -0
- package/dist/kradle-controller-ui.json +3482 -0
- package/dist/kradle-lifecycle.json +201 -0
- package/dist/kradle-runtime-snapshot.json +3125 -0
- package/dist/kradle-summary.json +724 -0
- package/docs/README.md +61 -0
- package/docs/agents/README.md +83 -0
- package/docs/agents/acceptance-test-matrix.md +193 -0
- package/docs/agents/agent-mux-adapter-contract.md +167 -0
- package/docs/agents/agent-mux-source-map.md +310 -0
- package/docs/agents/agent-run-memory-import-spec.md +256 -0
- package/docs/agents/agent-stack-management-spec.md +421 -0
- package/docs/agents/api-contract-spec.md +309 -0
- package/docs/agents/artifacts-writeback-spec.md +145 -0
- package/docs/agents/chart-packaging-spec.md +128 -0
- package/docs/agents/ci-orchestration-spec.md +140 -0
- package/docs/agents/context-assembly-spec.md +219 -0
- package/docs/agents/controller-reconciliation-spec.md +255 -0
- package/docs/agents/crd-schema-spec.md +315 -0
- package/docs/agents/decision-log-open-questions.md +169 -0
- package/docs/agents/developer-implementation-checklist.md +329 -0
- package/docs/agents/dispatching-design.md +262 -0
- package/docs/agents/gaps-agent-mux-to-kradle-crds.md +298 -0
- package/docs/agents/glossary.md +66 -0
- package/docs/agents/implementation-blueprint.md +324 -0
- package/docs/agents/implementation-rollout-slices.md +251 -0
- package/docs/agents/memory-context-integration-spec.md +194 -0
- package/docs/agents/memory-ontology-schema-spec.md +253 -0
- package/docs/agents/memory-operations-runbook.md +121 -0
- package/docs/agents/mvp-vertical-slice-spec.md +146 -0
- package/docs/agents/observability-audit-spec.md +265 -0
- package/docs/agents/operator-runbook.md +174 -0
- package/docs/agents/org-memory-api-payload-examples.md +333 -0
- package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
- package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
- package/docs/agents/org-memory-ui-implementation-map.md +114 -0
- package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
- package/docs/agents/org-resource-model-delta-spec.md +111 -0
- package/docs/agents/org-route-resource-model-spec.md +183 -0
- package/docs/agents/org-scoping-namespace-spec.md +114 -0
- package/docs/agents/rbac-secrets-management-spec.md +406 -0
- package/docs/agents/repository-page-integration-spec.md +255 -0
- package/docs/agents/resource-contract-examples.md +808 -0
- package/docs/agents/resource-relationship-map.md +190 -0
- package/docs/agents/security-threat-model.md +188 -0
- package/docs/agents/shared-memory-company-brain-spec.md +358 -0
- package/docs/agents/storage-migration-spec.md +168 -0
- package/docs/agents/subagent-orchestration-spec.md +152 -0
- package/docs/agents/system-overview.md +88 -0
- package/docs/agents/tools-mcp-skills-spec.md +189 -0
- package/docs/agents/traceability-matrix.md +79 -0
- package/docs/agents/ui-flow-spec.md +211 -0
- package/docs/agents/ui-ux-system-spec.md +426 -0
- package/docs/agents/workspace-lifecycle-spec.md +166 -0
- package/docs/architecture-spec.md +78 -0
- package/docs/architecture-v2.md +2759 -0
- package/docs/components/control-plane.md +78 -0
- package/docs/components/data-plane.md +69 -0
- package/docs/components/hooks-events.md +67 -0
- package/docs/components/identity-rbac-policy.md +73 -0
- package/docs/components/kubevela-oam.md +70 -0
- package/docs/components/operations-publishing.md +81 -0
- package/docs/components/runners-ci.md +66 -0
- package/docs/components/web-ui.md +94 -0
- package/docs/crd-behaviors-and-relationships.md +3926 -0
- package/docs/external/README.md +47 -0
- package/docs/external/bidirectional-sync-design.md +134 -0
- package/docs/external/cicd-interface.md +64 -0
- package/docs/external/external-backend-controllers.md +170 -0
- package/docs/external/external-backend-crds.md +234 -0
- package/docs/external/external-backend-ui-spec.md +151 -0
- package/docs/external/external-backend-ux-flows.md +115 -0
- package/docs/external/external-object-mapping.md +125 -0
- package/docs/external/git-forge-interface.md +68 -0
- package/docs/external/github-integration-design.md +151 -0
- package/docs/external/issue-tracking-interface.md +66 -0
- package/docs/external/provider-capability-manifests.md +204 -0
- package/docs/external/provider-catalog.md +139 -0
- package/docs/external/provider-rollout-testing.md +78 -0
- package/docs/external/research-results.md +48 -0
- package/docs/external/security-auth-permissions.md +81 -0
- package/docs/external/sync-state-machines.md +108 -0
- package/docs/external/unified-external-backend-model.md +107 -0
- package/docs/external/user-facing-changes.md +67 -0
- package/docs/gaps.md +161 -0
- package/docs/install.md +94 -0
- package/docs/integration-and-design-decisions.md +1530 -0
- package/docs/kradle-design.md +334 -0
- package/docs/local-minikube.md +55 -0
- package/docs/ontology/README.md +32 -0
- package/docs/ontology/bounded-contexts.md +29 -0
- package/docs/ontology/events-and-hooks.md +32 -0
- package/docs/ontology/oam-kubevela.md +32 -0
- package/docs/ontology/operations-and-release.md +25 -0
- package/docs/ontology/personas-and-actors.md +32 -0
- package/docs/ontology/policies-and-invariants.md +33 -0
- package/docs/ontology/problem-space.md +30 -0
- package/docs/ontology/resource-contracts.md +40 -0
- package/docs/ontology/resource-taxonomy.md +42 -0
- package/docs/ontology/runners-and-ci.md +29 -0
- package/docs/ontology/solution-space.md +24 -0
- package/docs/ontology/storage-and-data-boundaries.md +29 -0
- package/docs/ontology/validation-matrix.md +24 -0
- package/docs/ontology/web-ui-excellent-flows.md +32 -0
- package/docs/ontology/workflows.md +39 -0
- package/docs/ontology/world.md +35 -0
- package/docs/openapi.yaml +1291 -0
- package/docs/product-requirements.md +62 -0
- package/docs/requirements-v2.md +235 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/sdk-api-reference.md +1108 -0
- package/docs/system-requirements.md +90 -0
- package/docs/system-spec-v2.md +1230 -0
- package/docs/tests/README.md +53 -0
- package/docs/tests/agent-qa-plan.md +63 -0
- package/docs/tests/browser-ui-tests.md +62 -0
- package/docs/tests/ci-quality-gates.md +48 -0
- package/docs/tests/coverage-model.md +64 -0
- package/docs/tests/e2e-scenario-tests.md +53 -0
- package/docs/tests/fixtures-test-data.md +63 -0
- package/docs/tests/observability-reliability-tests.md +54 -0
- package/docs/tests/product-test-matrix.md +145 -0
- package/docs/tests/qa-adoption-roadmap.md +130 -0
- package/docs/tests/qa-automation-plan.md +101 -0
- package/docs/tests/security-compliance-tests.md +57 -0
- package/docs/tests/test-framework-tools.md +88 -0
- package/docs/tests/test-suite-layout.md +121 -0
- package/docs/tests/unit-integration-tests.md +48 -0
- package/docs/todo-kyverno +714 -0
- package/docs/todos.md +4 -0
- package/docs/user-stories.md +78 -0
- package/docs/web-console-spec.md +533 -0
- package/examples/minikube-demo.yaml +190 -0
- package/examples/oam-application.yaml +23 -0
- package/examples/policy-kyverno-pr-title.yaml +18 -0
- package/package.json +66 -0
- package/scripts/build.mjs +29 -0
- package/scripts/setup-minikube.mjs +65 -0
- package/scripts/smoke.mjs +37 -0
- package/scripts/validate-doc-coverage.mjs +152 -0
- package/scripts/validate-package.mjs +95 -0
- package/scripts/validate-ui.mjs +305 -0
- package/src/agent-adapter-controller.js +169 -0
- package/src/agent-approval-controller.js +170 -0
- package/src/agent-context-bundles.js +242 -0
- package/src/agent-dispatch-controller.js +549 -0
- package/src/agent-gateway-config-controller.js +147 -0
- package/src/agent-identity-migration.js +115 -0
- package/src/agent-memory-controller.js +357 -0
- package/src/agent-memory-import.js +327 -0
- package/src/agent-memory-query.js +292 -0
- package/src/agent-memory-repository-source-controller.js +255 -0
- package/src/agent-mux-client.js +589 -0
- package/src/agent-permission-review.js +250 -0
- package/src/agent-persona-controller.js +135 -0
- package/src/agent-project-controller.js +117 -0
- package/src/agent-prompt-composition.js +55 -0
- package/src/agent-provider-config-controller.js +151 -0
- package/src/agent-secret-config-grant-controller.js +282 -0
- package/src/agent-session-transcript-controller.js +189 -0
- package/src/agent-stack-controller.js +421 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +387 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +621 -0
- package/src/argocd-gitops.js +43 -0
- package/src/artifact-registry-controller.js +542 -0
- package/src/assistant-runtime.js +284 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +310 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +112 -0
- package/src/controller-ui.js +620 -0
- package/src/data-plane.js +179 -0
- package/src/event-bus.js +397 -0
- package/src/external/conflict-controller.js +225 -0
- package/src/external/github/auth.js +96 -0
- package/src/external/github/cicd.js +180 -0
- package/src/external/github/git-forge.js +240 -0
- package/src/external/github/index.js +144 -0
- package/src/external/github/issue-tracking.js +163 -0
- package/src/external/provider-adapter.js +161 -0
- package/src/external/provider-resource-factory.js +221 -0
- package/src/external/sync-controller.js +235 -0
- package/src/external/webhook-controller.js +144 -0
- package/src/external/write-controller.js +283 -0
- package/src/gitea-backend.js +131 -0
- package/src/gitea-service.js +173 -0
- package/src/handoff.js +98 -0
- package/src/health-probes.js +134 -0
- package/src/hooks-events.js +63 -0
- package/src/hooks-lifecycle.js +117 -0
- package/src/http-server.js +409 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +71 -0
- package/src/jitsi-agent-bridge.js +141 -0
- package/src/jitsi-meeting-controller.js +291 -0
- package/src/jitsi-sync-controller.js +198 -0
- package/src/kradle-inference-service-controller.js +246 -0
- package/src/kubernetes-controller-async.js +531 -0
- package/src/kubernetes-controller.js +904 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/model-route-controller.js +364 -0
- package/src/notification-controller.js +178 -0
- package/src/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +282 -0
- package/src/runner-controller.js +272 -0
- package/src/runners-ci.js +48 -0
- package/src/runtime.js +196 -0
- package/src/snapshot-cache.js +157 -0
- package/src/virtual-model-controller.js +538 -0
- package/src/virtual-model-hook-bridge.js +200 -0
- package/src/web-ui.js +40 -0
- package/tests/agent-adapter-controller.test.js +361 -0
- package/tests/agent-approval-controller.test.js +173 -0
- package/tests/agent-context-bundles.test.js +278 -0
- package/tests/agent-dispatch-controller.test.js +679 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-identity-migration.test.js +87 -0
- package/tests/agent-memory-controller.test.js +461 -0
- package/tests/agent-memory-import-snapshot.test.js +477 -0
- package/tests/agent-memory-query.test.js +404 -0
- package/tests/agent-memory-repository-source.test.js +514 -0
- package/tests/agent-mux-client.test.js +389 -0
- package/tests/agent-mux-integration.test.js +971 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-persona-controller.test.js +127 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-prompt-composition.test.js +76 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +303 -0
- package/tests/agent-secret-config-grant.test.js +231 -0
- package/tests/agent-session-transcript-controller.test.js +499 -0
- package/tests/agent-stack-controller.test.js +283 -0
- package/tests/agent-subagent-controller.test.js +201 -0
- package/tests/agent-transport-binding-controller.test.js +294 -0
- package/tests/agent-trigger-controller.test.js +271 -0
- package/tests/agent-trigger-routes.test.js +190 -0
- package/tests/agent-trigger-sources.test.js +245 -0
- package/tests/agent-workspace-controller.test.js +181 -0
- package/tests/agent-writeback.test.js +292 -0
- package/tests/approval-persistence.test.js +171 -0
- package/tests/artifact-registry.test.js +511 -0
- package/tests/assistant-runtime.test.js +506 -0
- package/tests/async-controller.test.js +252 -0
- package/tests/audit-controller.test.js +227 -0
- package/tests/codespace-controller.test.js +318 -0
- package/tests/controller-client.test.js +133 -0
- package/tests/deployment.test.js +527 -0
- package/tests/e2e/lifecycle.test.js +120 -0
- package/tests/event-bus-integration.test.js +355 -0
- package/tests/external-github-forge.test.js +560 -0
- package/tests/external-github-issues-cicd.test.js +520 -0
- package/tests/external-integration.test.js +470 -0
- package/tests/external-persistence.test.js +415 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +223 -0
- package/tests/external-webhook-sync.test.js +287 -0
- package/tests/external-write-conflict.test.js +353 -0
- package/tests/gitea-service.test.js +253 -0
- package/tests/health-check-real.test.js +165 -0
- package/tests/health-probes.test.js +90 -0
- package/tests/hooks-lifecycle.test.js +364 -0
- package/tests/integration/full-flow.test.js +266 -0
- package/tests/jitsi-agent-bridge.test.js +119 -0
- package/tests/jitsi-helm-integration.test.js +77 -0
- package/tests/jitsi-meeting-controller.test.js +170 -0
- package/tests/jitsi-resource-model.test.js +73 -0
- package/tests/jitsi-sync-controller.test.js +112 -0
- package/tests/kradle-inference-service.test.js +689 -0
- package/tests/kradle.test.js +779 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/model-route-controller.test.js +733 -0
- package/tests/notification-controller.test.js +196 -0
- package/tests/notification-integration.test.js +179 -0
- package/tests/org-scoping.test.js +687 -0
- package/tests/runner-controller.test.js +327 -0
- package/tests/runner-integration.test.js +231 -0
- package/tests/session-cookie-hmac.test.js +151 -0
- package/tests/snapshot-performance.test.js +315 -0
- package/tests/sse-events.test.js +107 -0
- package/tests/virtual-model-controller.test.js +877 -0
- package/tests/virtual-model-hook-bridge.test.js +384 -0
- package/tests/webhook-trigger.test.js +198 -0
- package/tests/workspace-volumes.test.js +312 -0
- package/tests/writeback-persistence.test.js +207 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import { URL } from 'node:url';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { createResource } from './resource-model.js';
|
|
6
|
+
|
|
7
|
+
export const AGENT_MUX_CLIENT_BOUNDARY = {
|
|
8
|
+
role: 'agent-mux-client',
|
|
9
|
+
scope: 'HTTP/SSE adapter for Agent Mux gateway — capabilities, sessions, events, transcripts, K8s Job dispatch',
|
|
10
|
+
owns: ['gateway HTTP calls', 'SSE event streaming', 'transcript reconciliation', 'K8s Job manifest generation', 'Job lifecycle management'],
|
|
11
|
+
delegatesTo: ['resource-model', 'kubernetes-resource-gateway'],
|
|
12
|
+
mustNotOwn: ['secret values', 'permission review', 'resource persistence']
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Known agent adapter names for job dispatch. */
|
|
16
|
+
const KNOWN_ADAPTERS = new Set([
|
|
17
|
+
'claude-code', 'codex', 'gemini-cli', 'aider', 'goose',
|
|
18
|
+
'amp', 'roo-code', 'kilo-code', 'cline', 'cursor',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const JITSI_SOCKET_PATH = '/tmp/jitsi-agent.sock';
|
|
22
|
+
|
|
23
|
+
function jitsiResourceProfile(audioMode = 'listen') {
|
|
24
|
+
if (audioMode === 'both') {
|
|
25
|
+
return {
|
|
26
|
+
requests: { cpu: '500m', memory: '512Mi' },
|
|
27
|
+
limits: { cpu: '2000m', memory: '2Gi' },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (audioMode === 'speak') {
|
|
31
|
+
return {
|
|
32
|
+
requests: { cpu: '500m', memory: '512Mi' },
|
|
33
|
+
limits: { cpu: '2000m', memory: '2Gi' },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (audioMode === 'listen') {
|
|
37
|
+
return {
|
|
38
|
+
requests: { cpu: '200m', memory: '256Mi' },
|
|
39
|
+
limits: { cpu: '1000m', memory: '1Gi' },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
requests: { cpu: '50m', memory: '128Mi' },
|
|
44
|
+
limits: { cpu: '200m', memory: '256Mi' },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createJitsiSidecarContainer(jitsi = {}) {
|
|
49
|
+
const capabilities = jitsi.capabilities || {};
|
|
50
|
+
const tts = jitsi.tts || {};
|
|
51
|
+
const stt = jitsi.stt || {};
|
|
52
|
+
const vad = jitsi.vad || {};
|
|
53
|
+
const audioMode = capabilities.audio || jitsi.audioMode || 'listen';
|
|
54
|
+
const chatMode = capabilities.chat || jitsi.chatMode || 'read';
|
|
55
|
+
const screenshareMode = capabilities.screenshare || jitsi.screenshareMode || 'none';
|
|
56
|
+
const env = [
|
|
57
|
+
{ name: 'JITSI_ROOM_URL', value: jitsi.roomUrl || '' },
|
|
58
|
+
{ name: 'JITSI_JWT', value: jitsi.jwt || '' },
|
|
59
|
+
{ name: 'JITSI_ROOM_ID', value: jitsi.roomId || '' },
|
|
60
|
+
{ name: 'JITSI_PARTICIPANT_NAME', value: jitsi.participantName || jitsi.stackName || 'Kradle Agent' },
|
|
61
|
+
{ name: 'JITSI_PARTICIPANT_ROLE', value: jitsi.role || 'observer' },
|
|
62
|
+
{ name: 'JITSI_GOODBYE_MESSAGE', value: jitsi.goodbyeMessage || 'Kradle agent is leaving the meeting.' },
|
|
63
|
+
{ name: 'JITSI_AUDIO_MODE', value: audioMode },
|
|
64
|
+
{ name: 'JITSI_CHAT_MODE', value: chatMode },
|
|
65
|
+
{ name: 'JITSI_SCREENSHARE_MODE', value: screenshareMode },
|
|
66
|
+
{ name: 'AGENT_SOCKET_PATH', value: JITSI_SOCKET_PATH },
|
|
67
|
+
];
|
|
68
|
+
if (tts.provider) env.push({ name: 'JITSI_TTS_PROVIDER', value: tts.provider });
|
|
69
|
+
if (tts.voice) env.push({ name: 'JITSI_TTS_VOICE', value: tts.voice });
|
|
70
|
+
if (tts.speed) env.push({ name: 'JITSI_TTS_SPEED', value: String(tts.speed) });
|
|
71
|
+
if (stt.provider) env.push({ name: 'JITSI_STT_PROVIDER', value: stt.provider });
|
|
72
|
+
if (vad.provider) env.push({ name: 'JITSI_VAD_PROVIDER', value: vad.provider });
|
|
73
|
+
if (jitsi.goodbyeMessage) env.push({ name: 'JITSI_GOODBYE_MESSAGE', value: jitsi.goodbyeMessage });
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
name: 'jitsi-agent-sidecar',
|
|
77
|
+
image: jitsi.sidecarImage || 'kradle/jitsi-agent-sidecar:latest',
|
|
78
|
+
env,
|
|
79
|
+
resources: jitsi.resources || jitsiResourceProfile(audioMode),
|
|
80
|
+
volumeMounts: [{ name: 'agent-socket', mountPath: '/tmp' }],
|
|
81
|
+
lifecycle: {
|
|
82
|
+
preStop: {
|
|
83
|
+
exec: {
|
|
84
|
+
command: ['node', 'bin/graceful-leave.mjs'],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Internal HTTP request helper. Zero external deps — uses node:http / node:https.
|
|
93
|
+
* @param {string} url
|
|
94
|
+
* @param {{ method?: string, body?: object, headers?: Record<string,string>, timeout?: number }} options
|
|
95
|
+
* @returns {Promise<{ status: number, body: any }>}
|
|
96
|
+
*/
|
|
97
|
+
function httpRequest(url, { method = 'GET', body, headers = {}, timeout = 30000 } = {}) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const parsed = new URL(url);
|
|
100
|
+
const transport = parsed.protocol === 'https:' ? https : http;
|
|
101
|
+
const opts = {
|
|
102
|
+
hostname: parsed.hostname,
|
|
103
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
104
|
+
path: parsed.pathname + parsed.search,
|
|
105
|
+
method,
|
|
106
|
+
headers: { 'Accept': 'application/json', ...headers },
|
|
107
|
+
timeout,
|
|
108
|
+
};
|
|
109
|
+
if (body) {
|
|
110
|
+
const payload = JSON.stringify(body);
|
|
111
|
+
opts.headers['Content-Type'] = 'application/json';
|
|
112
|
+
opts.headers['Content-Length'] = Buffer.byteLength(payload);
|
|
113
|
+
}
|
|
114
|
+
const req = transport.request(opts, (res) => {
|
|
115
|
+
const chunks = [];
|
|
116
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
117
|
+
res.on('end', () => {
|
|
118
|
+
const raw = Buffer.concat(chunks).toString();
|
|
119
|
+
try {
|
|
120
|
+
resolve({ status: res.statusCode, body: JSON.parse(raw) });
|
|
121
|
+
} catch {
|
|
122
|
+
resolve({ status: res.statusCode, body: raw });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
|
|
127
|
+
req.on('error', reject);
|
|
128
|
+
if (body) req.write(JSON.stringify(body));
|
|
129
|
+
req.end();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Parse SSE text into an array of parsed JSON data payloads.
|
|
135
|
+
* Each `data: {...}` line is extracted; malformed JSON is silently skipped.
|
|
136
|
+
* @param {string} text
|
|
137
|
+
* @returns {object[]}
|
|
138
|
+
*/
|
|
139
|
+
export function parseSseLines(text) {
|
|
140
|
+
const events = [];
|
|
141
|
+
for (const block of text.split('\n\n')) {
|
|
142
|
+
for (const line of block.split('\n')) {
|
|
143
|
+
if (line.startsWith('data: ')) {
|
|
144
|
+
try { events.push(JSON.parse(line.slice(6))); } catch { /* skip malformed */ }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return events;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Map provider name to codec identifier.
|
|
153
|
+
* @param {string} provider
|
|
154
|
+
* @returns {'anthropic'|'openai'|'google'}
|
|
155
|
+
*/
|
|
156
|
+
function deriveCodec(provider) {
|
|
157
|
+
const map = { anthropic: 'anthropic', openai: 'openai', google: 'google', gemini: 'google' };
|
|
158
|
+
return map[provider] || 'anthropic';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param {{ gateway?: string, enabled?: boolean, resourceGateway?: object }} options
|
|
163
|
+
*/
|
|
164
|
+
export function createAgentMuxClient(options = {}) {
|
|
165
|
+
const { gateway = '', enabled = false, resourceGateway = null } = options;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
role: 'agent-mux-client',
|
|
169
|
+
|
|
170
|
+
isAvailable() {
|
|
171
|
+
return enabled && !!gateway;
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resolve the transport config for a stack from its AgentTransportBinding.
|
|
176
|
+
* Defaults to 'stdio' for local subprocess adapters when no binding is found.
|
|
177
|
+
*
|
|
178
|
+
* @param {object} stack - AgentStack resource or plain spec object
|
|
179
|
+
* @param {object[]} transportBindings - Array of AgentTransportBinding resources
|
|
180
|
+
* @returns {{ protocol: string, endpoint: string, codec: string }}
|
|
181
|
+
*/
|
|
182
|
+
resolveTransport(stack, transportBindings = []) {
|
|
183
|
+
const adapterName = stack?.spec?.adapter || stack?.spec?.baseAgent || 'claude-code';
|
|
184
|
+
const provider = stack?.spec?.provider || 'anthropic';
|
|
185
|
+
const binding = (transportBindings || []).find(b => b.spec?.adapterRef === adapterName);
|
|
186
|
+
|
|
187
|
+
if (binding) {
|
|
188
|
+
const protocol = binding.spec.protocol || 'http';
|
|
189
|
+
const endpoint = binding.spec.endpoint || '';
|
|
190
|
+
const codec = deriveCodec(provider);
|
|
191
|
+
return { protocol, endpoint, codec };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { protocol: 'stdio', endpoint: '', codec: deriveCodec(provider) };
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Query adapter capabilities from the gateway.
|
|
199
|
+
* GET {gateway}/api/v1/agents/{adapter}/capabilities
|
|
200
|
+
* @param {string} adapter
|
|
201
|
+
* @returns {Promise<object|null>}
|
|
202
|
+
*/
|
|
203
|
+
async queryCapabilities(adapter) {
|
|
204
|
+
if (!this.isAvailable()) return null;
|
|
205
|
+
try {
|
|
206
|
+
const { status, body } = await httpRequest(`${gateway}/api/v1/agents/${encodeURIComponent(adapter)}/capabilities`);
|
|
207
|
+
if (status >= 200 && status < 300) return body;
|
|
208
|
+
return null;
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Launch a new agent session through the gateway.
|
|
216
|
+
* POST {gateway}/api/v1/sessions
|
|
217
|
+
* @param {{ stack: object, contextBundle?: object, permissionSnapshot?: object, workspace?: object }} params
|
|
218
|
+
* @returns {Promise<{ runId: string, sessionId: string }|null>}
|
|
219
|
+
*/
|
|
220
|
+
async launchSession({ stack, contextBundle, permissionSnapshot, workspace }) {
|
|
221
|
+
if (!this.isAvailable()) return null;
|
|
222
|
+
try {
|
|
223
|
+
const payload = {
|
|
224
|
+
agent: stack?.baseAgent,
|
|
225
|
+
model: stack?.model,
|
|
226
|
+
prompt: contextBundle?.prompt,
|
|
227
|
+
systemPrompt: contextBundle?.systemPrompt,
|
|
228
|
+
attachments: contextBundle?.attachments,
|
|
229
|
+
workspace: workspace?.mountPath || '/workspace',
|
|
230
|
+
};
|
|
231
|
+
const { status, body } = await httpRequest(`${gateway}/api/v1/sessions`, { method: 'POST', body: payload });
|
|
232
|
+
if (status >= 200 && status < 300 && body?.runId && body?.sessionId) {
|
|
233
|
+
return { runId: body.runId, sessionId: body.sessionId };
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get session status from the gateway.
|
|
243
|
+
* GET {gateway}/api/v1/sessions/{sessionId}
|
|
244
|
+
* @param {string} sessionId
|
|
245
|
+
* @returns {Promise<object|null>}
|
|
246
|
+
*/
|
|
247
|
+
async getSessionStatus(sessionId) {
|
|
248
|
+
if (!this.isAvailable()) return null;
|
|
249
|
+
try {
|
|
250
|
+
const { status, body } = await httpRequest(`${gateway}/api/v1/sessions/${encodeURIComponent(sessionId)}`);
|
|
251
|
+
if (status >= 200 && status < 300) return body;
|
|
252
|
+
return null;
|
|
253
|
+
} catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Subscribe to SSE events for a run. Reconnects with exponential backoff (1s, 2s, 4s... max 30s).
|
|
260
|
+
* GET {gateway}/api/v1/runs/{runId}/events (Accept: text/event-stream)
|
|
261
|
+
* @param {string} runId
|
|
262
|
+
* @param {(event: object) => void} callback
|
|
263
|
+
* @returns {{ abort: () => void }}
|
|
264
|
+
*/
|
|
265
|
+
subscribeToEvents(runId, callback) {
|
|
266
|
+
let aborted = false;
|
|
267
|
+
let currentReq = null;
|
|
268
|
+
let backoff = 1000;
|
|
269
|
+
|
|
270
|
+
const connect = () => {
|
|
271
|
+
if (aborted) return;
|
|
272
|
+
try {
|
|
273
|
+
const parsed = new URL(`${gateway}/api/v1/runs/${encodeURIComponent(runId)}/events`);
|
|
274
|
+
const transport = parsed.protocol === 'https:' ? https : http;
|
|
275
|
+
const opts = {
|
|
276
|
+
hostname: parsed.hostname,
|
|
277
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
278
|
+
path: parsed.pathname + parsed.search,
|
|
279
|
+
method: 'GET',
|
|
280
|
+
headers: { 'Accept': 'text/event-stream' },
|
|
281
|
+
};
|
|
282
|
+
currentReq = transport.request(opts, (res) => {
|
|
283
|
+
if (aborted) return;
|
|
284
|
+
// Reset backoff on successful connection
|
|
285
|
+
backoff = 1000;
|
|
286
|
+
let buffer = '';
|
|
287
|
+
res.on('data', (chunk) => {
|
|
288
|
+
if (aborted) return;
|
|
289
|
+
buffer += chunk.toString();
|
|
290
|
+
// Process complete SSE blocks (separated by double newlines)
|
|
291
|
+
const parts = buffer.split('\n\n');
|
|
292
|
+
// Keep the last part as it may be incomplete
|
|
293
|
+
buffer = parts.pop() || '';
|
|
294
|
+
for (const block of parts) {
|
|
295
|
+
for (const line of block.split('\n')) {
|
|
296
|
+
if (line.startsWith('data: ')) {
|
|
297
|
+
try {
|
|
298
|
+
callback(JSON.parse(line.slice(6)));
|
|
299
|
+
} catch { /* skip malformed */ }
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
res.on('end', () => {
|
|
305
|
+
if (!aborted) reconnect();
|
|
306
|
+
});
|
|
307
|
+
res.on('error', () => {
|
|
308
|
+
if (!aborted) reconnect();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
currentReq.on('error', () => {
|
|
312
|
+
if (!aborted) reconnect();
|
|
313
|
+
});
|
|
314
|
+
currentReq.end();
|
|
315
|
+
} catch {
|
|
316
|
+
if (!aborted) reconnect();
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const reconnect = () => {
|
|
321
|
+
if (aborted) return;
|
|
322
|
+
const delay = backoff;
|
|
323
|
+
backoff = Math.min(backoff * 2, 30000);
|
|
324
|
+
setTimeout(connect, delay);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
connect();
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
abort() {
|
|
331
|
+
aborted = true;
|
|
332
|
+
if (currentReq) {
|
|
333
|
+
currentReq.destroy();
|
|
334
|
+
currentReq = null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Generate a Kubernetes Job manifest to run an agent as an isolated Job
|
|
342
|
+
* instead of a subprocess of the API server.
|
|
343
|
+
*
|
|
344
|
+
* @param {{ adapter: string, provider?: string, model?: string, workspace?: { pvcName?: string }, prompt?: { system?: string, task?: string }, env?: Record<string,string>, org: string, runId?: string, stackName?: string, budget?: { maxDurationSeconds?: number }, resources?: object, image?: string, serviceAccount?: string, callbackUrl?: string, jitsi?: object, meetingContext?: object }} config
|
|
345
|
+
* @returns {{ jobManifest: object, jobName: string }}
|
|
346
|
+
*/
|
|
347
|
+
createAgentJob(config = {}) {
|
|
348
|
+
const {
|
|
349
|
+
adapter,
|
|
350
|
+
provider = 'anthropic',
|
|
351
|
+
model,
|
|
352
|
+
org,
|
|
353
|
+
runId = randomUUID(),
|
|
354
|
+
stackName,
|
|
355
|
+
budget,
|
|
356
|
+
image,
|
|
357
|
+
serviceAccount,
|
|
358
|
+
callbackUrl,
|
|
359
|
+
prompt,
|
|
360
|
+
env = {},
|
|
361
|
+
workspace,
|
|
362
|
+
resources: resourceLimits,
|
|
363
|
+
transportBindings = [],
|
|
364
|
+
jitsi,
|
|
365
|
+
meetingContext = null,
|
|
366
|
+
} = config;
|
|
367
|
+
|
|
368
|
+
// Validate adapter
|
|
369
|
+
if (!adapter || typeof adapter !== 'string') {
|
|
370
|
+
throw new Error('createAgentJob requires a valid adapter name');
|
|
371
|
+
}
|
|
372
|
+
if (!KNOWN_ADAPTERS.has(adapter)) {
|
|
373
|
+
throw new Error(`Unknown adapter: ${adapter}. Known adapters: ${[...KNOWN_ADAPTERS].join(', ')}`);
|
|
374
|
+
}
|
|
375
|
+
if (!org) {
|
|
376
|
+
throw new Error('createAgentJob requires an org');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const jobName = `kradle-agent-${runId}`;
|
|
380
|
+
const pvcName = workspace?.pvcName;
|
|
381
|
+
|
|
382
|
+
const transportConfig = this.resolveTransport(
|
|
383
|
+
{ spec: { adapter, provider } },
|
|
384
|
+
transportBindings
|
|
385
|
+
);
|
|
386
|
+
const jitsiContext = meetingContext || jitsi || null;
|
|
387
|
+
|
|
388
|
+
const containerEnv = [
|
|
389
|
+
{ name: 'KRADLE_ORG', value: org },
|
|
390
|
+
{ name: 'KRADLE_RUN_ID', value: runId },
|
|
391
|
+
{ name: 'KRADLE_WORKSPACE_PATH', value: '/workspace' },
|
|
392
|
+
{ name: 'AGENT_MUX_TRANSPORT', value: transportConfig.protocol },
|
|
393
|
+
{ name: 'TRANSPORT_MUX_CODEC', value: transportConfig.codec },
|
|
394
|
+
...(transportConfig.endpoint ? [{ name: 'AGENT_MUX_TRANSPORT_ENDPOINT', value: transportConfig.endpoint }] : []),
|
|
395
|
+
...(callbackUrl ? [{ name: 'KRADLE_CALLBACK_URL', value: callbackUrl }] : []),
|
|
396
|
+
...(prompt?.system ? [{ name: 'AGENT_SYSTEM_PROMPT', value: prompt.system }] : []),
|
|
397
|
+
...(prompt?.task ? [{ name: 'AGENT_TASK', value: prompt.task }] : []),
|
|
398
|
+
...(jitsiContext ? [
|
|
399
|
+
{ name: 'JITSI_AGENT_SOCKET', value: JITSI_SOCKET_PATH },
|
|
400
|
+
{ name: 'JITSI_MEETING_ACTIVE', value: 'true' },
|
|
401
|
+
] : []),
|
|
402
|
+
...Object.entries(env).map(([name, value]) => ({ name, value: String(value) })),
|
|
403
|
+
];
|
|
404
|
+
const volumes = pvcName ? [{ name: 'workspace', persistentVolumeClaim: { claimName: pvcName } }] : [];
|
|
405
|
+
const agentVolumeMounts = pvcName ? [{ name: 'workspace', mountPath: '/workspace' }] : [];
|
|
406
|
+
if (jitsiContext) {
|
|
407
|
+
agentVolumeMounts.push({ name: 'agent-socket', mountPath: '/tmp' });
|
|
408
|
+
volumes.push({ name: 'agent-socket', emptyDir: {} });
|
|
409
|
+
}
|
|
410
|
+
const containers = [{
|
|
411
|
+
name: 'agent',
|
|
412
|
+
image: image || 'ghcr.io/a5c-ai/agent-mux:latest',
|
|
413
|
+
command: ['node', 'dist/cli/index.js', 'launch', adapter, provider],
|
|
414
|
+
args: model ? ['--model', model] : [],
|
|
415
|
+
env: containerEnv,
|
|
416
|
+
resources: resourceLimits || {
|
|
417
|
+
requests: { cpu: '500m', memory: '1Gi' },
|
|
418
|
+
limits: { cpu: '2', memory: '4Gi' },
|
|
419
|
+
},
|
|
420
|
+
volumeMounts: agentVolumeMounts,
|
|
421
|
+
}];
|
|
422
|
+
|
|
423
|
+
if (jitsiContext) {
|
|
424
|
+
containers.push(createJitsiSidecarContainer({ ...jitsiContext, stackName }));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const jobManifest = {
|
|
428
|
+
apiVersion: 'batch/v1',
|
|
429
|
+
kind: 'Job',
|
|
430
|
+
metadata: {
|
|
431
|
+
name: jobName,
|
|
432
|
+
namespace: `kradle-org-${org}`,
|
|
433
|
+
labels: {
|
|
434
|
+
'kradle.a5c.ai/component': 'agent-run',
|
|
435
|
+
'kradle.a5c.ai/run': runId,
|
|
436
|
+
...(stackName ? { 'kradle.a5c.ai/stack': stackName } : {}),
|
|
437
|
+
'kradle.a5c.ai/org': org,
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
spec: {
|
|
441
|
+
backoffLimit: 0,
|
|
442
|
+
activeDeadlineSeconds: budget?.maxDurationSeconds || 3600,
|
|
443
|
+
template: {
|
|
444
|
+
metadata: {
|
|
445
|
+
labels: {
|
|
446
|
+
'kradle.a5c.ai/component': 'agent-run',
|
|
447
|
+
'kradle.a5c.ai/run': runId,
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
spec: {
|
|
451
|
+
restartPolicy: 'Never',
|
|
452
|
+
serviceAccountName: serviceAccount || 'kradle',
|
|
453
|
+
containers,
|
|
454
|
+
volumes,
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
return { jobManifest, jobName };
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Submit a Job manifest to Kubernetes via the resource gateway.
|
|
465
|
+
*
|
|
466
|
+
* @param {object} jobManifest - Full K8s Job manifest
|
|
467
|
+
* @returns {Promise<{ jobName: string, namespace: string, submitted: boolean }>}
|
|
468
|
+
*/
|
|
469
|
+
async submitAgentJob(jobManifest) {
|
|
470
|
+
if (!resourceGateway) {
|
|
471
|
+
throw new Error('submitAgentJob requires a resourceGateway');
|
|
472
|
+
}
|
|
473
|
+
const jobName = jobManifest?.metadata?.name;
|
|
474
|
+
const namespace = jobManifest?.metadata?.namespace;
|
|
475
|
+
await resourceGateway.apply(jobManifest);
|
|
476
|
+
return { jobName, namespace, submitted: true };
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Get the status of a submitted K8s Job.
|
|
481
|
+
*
|
|
482
|
+
* @param {string} jobName
|
|
483
|
+
* @param {string} namespace
|
|
484
|
+
* @returns {Promise<{ active: number, succeeded: number, failed: number, startTime: string|null, completionTime: string|null, conditions: object[] }>}
|
|
485
|
+
*/
|
|
486
|
+
async getJobStatus(jobName, namespace) {
|
|
487
|
+
if (!resourceGateway) {
|
|
488
|
+
throw new Error('getJobStatus requires a resourceGateway');
|
|
489
|
+
}
|
|
490
|
+
const job = await resourceGateway.get('Job', jobName);
|
|
491
|
+
if (!job) {
|
|
492
|
+
return { active: 0, succeeded: 0, failed: 0, startTime: null, completionTime: null, conditions: [] };
|
|
493
|
+
}
|
|
494
|
+
const status = job.status || {};
|
|
495
|
+
return {
|
|
496
|
+
active: status.active || 0,
|
|
497
|
+
succeeded: status.succeeded || 0,
|
|
498
|
+
failed: status.failed || 0,
|
|
499
|
+
startTime: status.startTime || null,
|
|
500
|
+
completionTime: status.completionTime || null,
|
|
501
|
+
conditions: status.conditions || [],
|
|
502
|
+
};
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Retrieve logs from a K8s Job's pod.
|
|
507
|
+
*
|
|
508
|
+
* @param {string} jobName
|
|
509
|
+
* @param {string} namespace
|
|
510
|
+
* @returns {Promise<string>}
|
|
511
|
+
*/
|
|
512
|
+
async getJobLogs(jobName, namespace) {
|
|
513
|
+
if (!resourceGateway) {
|
|
514
|
+
throw new Error('getJobLogs requires a resourceGateway');
|
|
515
|
+
}
|
|
516
|
+
// Use the resource gateway's log retrieval (reads pod logs for the job)
|
|
517
|
+
if (typeof resourceGateway.getLogs === 'function') {
|
|
518
|
+
return resourceGateway.getLogs('Job', jobName, namespace);
|
|
519
|
+
}
|
|
520
|
+
// Fallback: return empty string if gateway doesn't support logs
|
|
521
|
+
return '';
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Delete a completed K8s Job and its pods.
|
|
526
|
+
*
|
|
527
|
+
* @param {string} jobName
|
|
528
|
+
* @param {string} namespace
|
|
529
|
+
* @returns {Promise<{ deleted: boolean }>}
|
|
530
|
+
*/
|
|
531
|
+
async deleteJob(jobName, namespace) {
|
|
532
|
+
if (!resourceGateway) {
|
|
533
|
+
throw new Error('deleteJob requires a resourceGateway');
|
|
534
|
+
}
|
|
535
|
+
await resourceGateway.delete('Job', jobName);
|
|
536
|
+
return { deleted: true };
|
|
537
|
+
},
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Reconcile SSE events into an AgentSessionTranscript resource.
|
|
541
|
+
* Parses events by role, computes cost, creates the resource via createResource().
|
|
542
|
+
* @param {string} sessionId
|
|
543
|
+
* @param {object[]} events
|
|
544
|
+
* @param {{ namespace?: string, organizationRef?: string }} options
|
|
545
|
+
* @returns {object} AgentSessionTranscript resource
|
|
546
|
+
*/
|
|
547
|
+
reconcileTranscript(sessionId, events, { namespace = 'default', organizationRef = 'default' } = {}) {
|
|
548
|
+
const messages = [];
|
|
549
|
+
let totalInputTokens = 0;
|
|
550
|
+
let totalOutputTokens = 0;
|
|
551
|
+
|
|
552
|
+
for (const event of events) {
|
|
553
|
+
if (!event || typeof event !== 'object') continue;
|
|
554
|
+
const role = event.role || 'unknown';
|
|
555
|
+
const content = event.content || event.text || event.message || '';
|
|
556
|
+
const node = {
|
|
557
|
+
role,
|
|
558
|
+
content: typeof content === 'string' ? content : JSON.stringify(content),
|
|
559
|
+
timestamp: event.timestamp || new Date().toISOString(),
|
|
560
|
+
};
|
|
561
|
+
if (event.toolUse) node.toolUse = event.toolUse;
|
|
562
|
+
if (event.toolResult) node.toolResult = event.toolResult;
|
|
563
|
+
messages.push(node);
|
|
564
|
+
|
|
565
|
+
// Accumulate token usage if present
|
|
566
|
+
if (event.usage) {
|
|
567
|
+
totalInputTokens += event.usage.inputTokens || 0;
|
|
568
|
+
totalOutputTokens += event.usage.outputTokens || 0;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return createResource(
|
|
573
|
+
'AgentSessionTranscript',
|
|
574
|
+
{ name: `transcript-${sessionId}`, namespace },
|
|
575
|
+
{
|
|
576
|
+
organizationRef,
|
|
577
|
+
sessionRef: sessionId,
|
|
578
|
+
messages,
|
|
579
|
+
cost: {
|
|
580
|
+
inputTokens: totalInputTokens,
|
|
581
|
+
outputTokens: totalOutputTokens,
|
|
582
|
+
totalTokens: totalInputTokens + totalOutputTokens,
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
{ phase: 'Reconciled', reconciledAt: new Date().toISOString() }
|
|
586
|
+
);
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
}
|