@noodleseed/one 0.136.0 → 0.138.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/assistant-ops.d.ts.map +1 -1
- package/dist/commands/assistant-ops.js +30 -1
- package/dist/commands/assistant-ops.js.map +1 -1
- package/dist/commands/deploy-first-flow.d.ts +2 -0
- package/dist/commands/deploy-first-flow.d.ts.map +1 -1
- package/dist/commands/deploy-first-flow.js +46 -14
- package/dist/commands/deploy-first-flow.js.map +1 -1
- package/dist/commands/deploy-ops.d.ts.map +1 -1
- package/dist/commands/deploy-ops.js +27 -1
- package/dist/commands/deploy-ops.js.map +1 -1
- package/dist/commands/deploy-preflight.d.ts +1 -0
- package/dist/commands/deploy-preflight.d.ts.map +1 -1
- package/dist/commands/deploy-preflight.js +1 -0
- package/dist/commands/deploy-preflight.js.map +1 -1
- package/dist/commands/deploy-status-ops.d.ts +1 -0
- package/dist/commands/deploy-status-ops.d.ts.map +1 -1
- package/dist/commands/deploy-status-ops.js +59 -4
- package/dist/commands/deploy-status-ops.js.map +1 -1
- package/dist/commands/deployments-ops.d.ts.map +1 -1
- package/dist/commands/deployments-ops.js +2 -0
- package/dist/commands/deployments-ops.js.map +1 -1
- package/dist/commands/diagnostics-ops.d.ts +1 -0
- package/dist/commands/diagnostics-ops.d.ts.map +1 -1
- package/dist/commands/diagnostics-ops.js +3 -0
- package/dist/commands/diagnostics-ops.js.map +1 -1
- package/dist/deploy.d.ts +3 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +4 -0
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +4 -4
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +22 -13
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +6 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +5 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +37 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +4 -0
- package/node_modules/@noodle-borg/{service/dist/assistant-model-stream.js → assistant-gateway/dist/model-stream.js} +34 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.d.ts +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.d.ts +4 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +6 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +11 -5
- package/node_modules/@noodle-borg/assistant-gateway/package.json +5 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +10 -2
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +4 -0
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +17 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-deployment.js +14 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +1 -7
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +20 -8
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -8
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/rollback-deployment.d.ts +61 -0
- package/node_modules/@noodle-borg/{service/dist/operations → control-plane/dist}/rollback-deployment.js +3 -2
- package/node_modules/@noodle-borg/control-plane/dist/rollback-result.d.ts +19 -0
- package/node_modules/@noodle-borg/control-plane/package.json +3 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/contracts.js +4 -0
- package/node_modules/@noodle-borg/developer-mcp/dist/widgets/app-overview.js +1 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/widgets/deployment-detail.js +1 -1
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/deployment-automation.d.ts +1 -1
- package/node_modules/@noodle-borg/module/dist/endpoint-url.d.ts +11 -0
- package/node_modules/@noodle-borg/module/dist/endpoint-url.js +20 -0
- package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/index.js +1 -0
- package/node_modules/@noodle-borg/observability/dist/assistant-usage.js +217 -0
- package/node_modules/@noodle-borg/observability/dist/index.js +2 -0
- package/node_modules/@noodle-borg/{service → observability}/dist/request-event-metrics.js +5 -2
- package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +6 -0
- package/node_modules/@noodle-borg/observability/dist/request-events.js +2 -0
- package/node_modules/@noodle-borg/service/dist/assistant-bindings.js +3 -1
- package/node_modules/@noodle-borg/service/dist/developer-mcp/control-plane-adapter.js +3 -3
- package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +2 -1
- package/node_modules/@noodle-borg/service/dist/registry-access.js +73 -24
- package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +5 -2
- package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +5 -9
- package/node_modules/@noodle-borg/service/dist/registry-helpers.js +4 -18
- package/node_modules/@noodle-borg/service/dist/registry-rollback.js +3 -1
- package/node_modules/@noodle-borg/service/dist/registry-status.js +4 -1
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -4
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +11 -16
- package/node_modules/@noodle-borg/service/dist/registry.d.ts +8 -5
- package/node_modules/@noodle-borg/service/dist/registry.js +8 -5
- package/node_modules/@noodle-borg/service/dist/routes/access.js +19 -1
- package/node_modules/@noodle-borg/service/dist/routes/analytics-dispatch.js +17 -1
- package/node_modules/@noodle-borg/service/dist/routes/analytics.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +73 -59
- package/node_modules/@noodle-borg/service/dist/routes/assistant-doctor.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +28 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +18 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-turn.js +6 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-usage.js +42 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +20 -3
- package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +49 -4
- package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +11 -1
- package/node_modules/@noodle-borg/service/dist/routes/endpoint-enrichment.js +2 -1
- package/node_modules/@noodle-borg/service/dist/routes/paths.js +4 -0
- package/node_modules/@noodle-borg/service/dist/routes/rollback.js +1 -1
- package/node_modules/@noodle-borg/service/dist/service.js +6 -0
- package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +2 -5
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +9 -21
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +2 -5
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -20
- package/node_modules/@noodle-borg/service/dist/store/records.js +39 -0
- package/node_modules/@noodle-borg/service/dist/store.d.ts +21 -10
- package/node_modules/@noodle-borg/service/package.json +5 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +120 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +123 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +4 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +15 -1
- package/node_modules/@noodleseed/assistant/README.md +43 -8
- package/node_modules/@noodleseed/assistant/package.json +16 -2
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { normalizeServerVersion } from '@noodle-borg/module';
|
|
2
2
|
import { accessUpdateRequestSchema } from '@noodle-borg/wire-contracts';
|
|
3
3
|
import { readJsonBody, sendJson } from '../http-util.js';
|
|
4
|
+
import { deploymentOwnerSubject } from '../registry-helpers.js';
|
|
4
5
|
import { authorizeControlPlane } from './control-plane.js';
|
|
5
6
|
import { canManageMembers } from './org-admin.js';
|
|
6
7
|
export async function handleAccessUpdate(req, res, registry, gate, controlPlane, audit, maxBody, tenant) {
|
|
@@ -30,7 +31,11 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
|
|
|
30
31
|
catch (error) {
|
|
31
32
|
return sendJson(res, 400, { error: error.message });
|
|
32
33
|
}
|
|
33
|
-
const result = await registry.updateAccess(tenant,
|
|
34
|
+
const result = await registry.updateAccess(tenant, {
|
|
35
|
+
accessMode: parsed.data.accessMode,
|
|
36
|
+
...(parsed.data.ownerSubject !== undefined ? { ownerSubject: parsed.data.ownerSubject } : {}),
|
|
37
|
+
...(serverVersion !== undefined ? { serverVersion } : {}),
|
|
38
|
+
});
|
|
34
39
|
if (!result.ok) {
|
|
35
40
|
return sendJson(res, result.status, {
|
|
36
41
|
error: result.message,
|
|
@@ -38,6 +43,7 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
|
|
|
38
43
|
});
|
|
39
44
|
}
|
|
40
45
|
const accessMode = result.record.accessMode ?? 'owner-only';
|
|
46
|
+
const ownerSubject = deploymentOwnerSubject(result.record);
|
|
41
47
|
await audit.emit({
|
|
42
48
|
eventType: 'deployment.access.updated',
|
|
43
49
|
org: tenant.org,
|
|
@@ -51,6 +57,12 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
|
|
|
51
57
|
details: {
|
|
52
58
|
previousAccessMode: result.previousAccessMode,
|
|
53
59
|
accessMode,
|
|
60
|
+
...(result.previousOwnerSubject !== undefined
|
|
61
|
+
? { previousOwnerSubject: result.previousOwnerSubject }
|
|
62
|
+
: {}),
|
|
63
|
+
...(ownerSubject !== undefined ? { ownerSubject } : {}),
|
|
64
|
+
accessChanged: result.accessChanged,
|
|
65
|
+
ownerChanged: result.ownerChanged,
|
|
54
66
|
changed: result.changed,
|
|
55
67
|
},
|
|
56
68
|
});
|
|
@@ -63,8 +75,14 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
|
|
|
63
75
|
? { serverVersion: result.record.serverVersion }
|
|
64
76
|
: {}),
|
|
65
77
|
accessMode,
|
|
78
|
+
...(ownerSubject !== undefined ? { ownerSubject } : {}),
|
|
66
79
|
},
|
|
67
80
|
previousAccessMode: result.previousAccessMode,
|
|
81
|
+
...(result.previousOwnerSubject !== undefined
|
|
82
|
+
? { previousOwnerSubject: result.previousOwnerSubject }
|
|
83
|
+
: {}),
|
|
84
|
+
accessChanged: result.accessChanged,
|
|
85
|
+
ownerChanged: result.ownerChanged,
|
|
68
86
|
changed: result.changed,
|
|
69
87
|
});
|
|
70
88
|
}
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import { handleTenantEvents, handleTenantMetrics, handleTenantSession } from './analytics.js';
|
|
2
|
-
import {
|
|
2
|
+
import { handleTenantAssistantUsage } from './assistant-usage.js';
|
|
3
|
+
import { parseTenantAssistantUsagePath, parseTenantEventsPath, parseTenantMetricsPath, parseTenantSessionPath, } from './paths.js';
|
|
3
4
|
/** Try the analytics read routes; returns true when the request was handled (or a guard ended it). */
|
|
4
5
|
export function dispatchAnalyticsReads(req, res, url, deps) {
|
|
5
6
|
const { gate, controlPlane, requestEventStore, applySecurityHeaders, enforceHttps, sendJson, tls, developerGrantStore, } = deps;
|
|
7
|
+
const assistantUsageRef = parseTenantAssistantUsagePath(url.pathname);
|
|
8
|
+
if (assistantUsageRef !== undefined && req.method === 'GET') {
|
|
9
|
+
applySecurityHeaders(res, tls);
|
|
10
|
+
if (enforceHttps(req, res, tls))
|
|
11
|
+
return true;
|
|
12
|
+
if (requestEventStore === undefined) {
|
|
13
|
+
sendJson(res, 409, { ok: false, error: 'assistant usage requires a configured event store' });
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
handleTenantAssistantUsage(req, res, gate, controlPlane, requestEventStore, assistantUsageRef, url, developerGrantStore).catch(() => {
|
|
17
|
+
if (!res.headersSent)
|
|
18
|
+
sendJson(res, 500, { error: 'internal error' });
|
|
19
|
+
});
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
6
22
|
const metricsRef = parseTenantMetricsPath(url.pathname);
|
|
7
23
|
if (metricsRef !== undefined && req.method === 'GET') {
|
|
8
24
|
applySecurityHeaders(res, tls);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { aggregateRequestEvents } from '@noodle-borg/observability';
|
|
1
2
|
import { normalizedTimestamps, sendJson } from '../http-util.js';
|
|
2
|
-
import { aggregateRequestEvents } from '../request-event-metrics.js';
|
|
3
3
|
import { authorizeTenantControl, developerGrantRouteAccess } from './control-plane.js';
|
|
4
4
|
/**
|
|
5
5
|
* Tenant analytics read routes (ADR 0121 Stage B): `metrics` (aggregated), `events` (the stream), and
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { ADMISSION_DEFAULTS } from '@noodle-borg/admission-limits/portable';
|
|
2
|
+
import { requestModelCompletion, } from '@noodle-borg/assistant-gateway/model-runtime';
|
|
2
3
|
import { dispatchAssistantTool, projectAssistantGuide, publicSurfaceOf, selectAssistantModelTools, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
4
|
import { validateJsonSchemaWithDefaults } from '@noodle-borg/compiler';
|
|
4
5
|
import { guardedFetch } from '@noodle-borg/connector-http';
|
|
5
6
|
import { evaluateToolAuthorization } from '@noodle-borg/protocol';
|
|
6
7
|
import { executeTool } from '@noodle-borg/runtime';
|
|
7
8
|
import { toolTouchesDelegatedAuth } from '../assistant-delegated-projection.js';
|
|
8
|
-
import { readModelCompletion, } from '../assistant-model-stream.js';
|
|
9
9
|
import { assistantViewAvailableData } from '../assistant-view-availability.js';
|
|
10
10
|
import { invocationContextSystemMessages } from '../invocation-context.js';
|
|
11
|
-
import { resolveManagedVariablesInString } from '../managed-config-expressions.js';
|
|
12
|
-
import { resolveConfigScope } from '../store.js';
|
|
13
11
|
import { interceptForElevation, offersSignIn } from './assistant-elevation.js';
|
|
14
12
|
import { assistantKnowledgeModelTools, executeAssistantKnowledgeSearch, findAssistantKnowledgeComponent, KNOWLEDGE_CITATION_GUIDANCE, resolveAssistantKnowledge, } from './assistant-knowledge.js';
|
|
13
|
+
import { resolveAssistantModelBinding } from './assistant-model-binding.js';
|
|
15
14
|
const MAX_MODEL_RESPONSE = 1 << 20;
|
|
16
15
|
/**
|
|
17
16
|
* The agent-loop bounds come from the admission envelope so an operator lowering them changes
|
|
@@ -22,21 +21,33 @@ function agentBounds(deps) {
|
|
|
22
21
|
const envelope = deps.admissionEnvelope ?? ADMISSION_DEFAULTS;
|
|
23
22
|
return { steps: envelope.modelStepsPerTurn, toolCalls: envelope.toolCallsPerTurn };
|
|
24
23
|
}
|
|
24
|
+
export function createAssistantTurnStats() {
|
|
25
|
+
return {
|
|
26
|
+
modelRequests: 0,
|
|
27
|
+
toolCalls: 0,
|
|
28
|
+
interactionCount: 0,
|
|
29
|
+
promptTokens: 0,
|
|
30
|
+
completionTokens: 0,
|
|
31
|
+
reasoningTokens: 0,
|
|
32
|
+
totalTokens: 0,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
25
35
|
/** Run one model/tool loop against the immutable invocation-context snapshot for this turn. */
|
|
26
|
-
export async function runAgentTurn(target, session, message, context, deps, emit, modelContext, pageContext) {
|
|
36
|
+
export async function runAgentTurn(target, session, message, context, deps, emit, modelContext, pageContext, stats = createAssistantTurnStats()) {
|
|
27
37
|
const assistant = target.served.artifact.server.assistant;
|
|
28
38
|
if (!assistant)
|
|
29
39
|
return emit({ event: 'error', data: { code: 'assistant_unavailable' } });
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
const binding = await resolveAssistantModelBinding(target, session.tenant, session.deploymentId, deps);
|
|
41
|
+
if (!binding) {
|
|
42
|
+
return emit({
|
|
43
|
+
event: 'error',
|
|
44
|
+
data: {
|
|
45
|
+
code: assistant.model.kind === 'noodle-managed'
|
|
46
|
+
? 'managed_model_unavailable'
|
|
47
|
+
: 'model_not_configured',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
40
51
|
const identityLine = signedInIdentityLine(session.caller, assistant.sessionClaims);
|
|
41
52
|
const knowledge = await resolveAssistantKnowledge(target.served);
|
|
42
53
|
const modelTools = selectTurnModelTools(target, session.caller);
|
|
@@ -125,8 +136,24 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
125
136
|
// Across every step of this turn, not per step: eight tool calls is eight, however the model splits
|
|
126
137
|
// them, or a model that loops one call per step would spend the budget a step at a time.
|
|
127
138
|
let toolCallsThisTurn = 0;
|
|
139
|
+
let remainingTokens = binding.requestPolicy?.maxTokensPerTurn;
|
|
140
|
+
const turnSignal = binding.requestPolicy?.maxTurnMs === undefined
|
|
141
|
+
? undefined
|
|
142
|
+
: AbortSignal.timeout(binding.requestPolicy.maxTurnMs);
|
|
128
143
|
for (let step = 0; step < bounds.steps; step += 1) {
|
|
129
|
-
|
|
144
|
+
if (remainingTokens !== undefined && remainingTokens <= 0) {
|
|
145
|
+
return emit({ event: 'error', data: { code: 'model_token_budget_exhausted' } });
|
|
146
|
+
}
|
|
147
|
+
const requestTokenLimit = Math.min(binding.requestPolicy?.maxCompletionTokens ?? Number.MAX_SAFE_INTEGER, remainingTokens ?? Number.MAX_SAFE_INTEGER);
|
|
148
|
+
const completion = await requestCompletion(binding, messages, target, session.caller, deps.modelFetch, (delta) => emit({ event: 'content', data: { delta } }), assistantKnowledgeModelTools(knowledge), modelTools, requestTokenLimit === Number.MAX_SAFE_INTEGER ? undefined : requestTokenLimit, turnSignal);
|
|
149
|
+
stats.modelRequests += 1;
|
|
150
|
+
stats.promptTokens += completion.usage?.promptTokens ?? 0;
|
|
151
|
+
stats.completionTokens += completion.usage?.completionTokens ?? 0;
|
|
152
|
+
stats.reasoningTokens += completion.usage?.reasoningTokens ?? 0;
|
|
153
|
+
stats.totalTokens += completion.usage?.totalTokens ?? 0;
|
|
154
|
+
if (remainingTokens !== undefined) {
|
|
155
|
+
remainingTokens = Math.max(0, remainingTokens - (completion.usage?.completionTokens ?? requestTokenLimit));
|
|
156
|
+
}
|
|
130
157
|
const response = completion.choices[0]?.message;
|
|
131
158
|
if (!response)
|
|
132
159
|
return emit({ event: 'error', data: { code: 'invalid_model_response' } });
|
|
@@ -139,6 +166,7 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
139
166
|
});
|
|
140
167
|
for (const call of response.tool_calls) {
|
|
141
168
|
toolCallsThisTurn += 1;
|
|
169
|
+
stats.toolCalls += 1;
|
|
142
170
|
if (toolCallsThisTurn > bounds.toolCalls) {
|
|
143
171
|
return emit({ event: 'error', data: { code: 'tool_call_budget_exhausted' } });
|
|
144
172
|
}
|
|
@@ -184,6 +212,7 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
184
212
|
now: assistantNow(deps),
|
|
185
213
|
});
|
|
186
214
|
if (elevation) {
|
|
215
|
+
stats.interactionCount += 1;
|
|
187
216
|
emit(elevation.event);
|
|
188
217
|
messages.push({
|
|
189
218
|
role: 'tool',
|
|
@@ -223,6 +252,7 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
223
252
|
onToolStarted: () => emit({ event: 'tool_started', data: { id: call.id, tool: tool.name } }),
|
|
224
253
|
});
|
|
225
254
|
if (dispatch.kind === 'event') {
|
|
255
|
+
stats.interactionCount += 1;
|
|
226
256
|
return emit({ event: dispatch.event, data: dispatch.data });
|
|
227
257
|
}
|
|
228
258
|
const view = assistantViewAvailableData(target.served.artifact, {
|
|
@@ -252,15 +282,8 @@ export async function narrateInteractionResolution(target, session, tool, action
|
|
|
252
282
|
const assistant = target.served.artifact.server.assistant;
|
|
253
283
|
if (!assistant)
|
|
254
284
|
return '';
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
deps.registry.configStore.resolveConfigValues('variable', scope),
|
|
258
|
-
deps.registry.configStore.resolveConfigValues('secret', scope),
|
|
259
|
-
]);
|
|
260
|
-
const baseUrl = resolveManagedVariablesInString(assistant.model.baseUrl, variables).replace(/\/$/, '');
|
|
261
|
-
const model = resolveManagedVariablesInString(assistant.model.model, variables);
|
|
262
|
-
const apiKey = secrets[assistant.model.apiKey];
|
|
263
|
-
if (!apiKey)
|
|
285
|
+
const binding = await resolveAssistantModelBinding(target, session.tenant, session.deploymentId, deps);
|
|
286
|
+
if (!binding)
|
|
264
287
|
return '';
|
|
265
288
|
const identityLine = signedInIdentityLine(session.caller, assistant.sessionClaims);
|
|
266
289
|
const knowledge = await resolveAssistantKnowledge(target.served);
|
|
@@ -285,7 +308,7 @@ export async function narrateInteractionResolution(target, session, tool, action
|
|
|
285
308
|
},
|
|
286
309
|
];
|
|
287
310
|
let narrated = '';
|
|
288
|
-
const completion = await requestCompletion(
|
|
311
|
+
const completion = await requestCompletion(binding, messages, target, session.caller, deps.modelFetch, (delta) => {
|
|
289
312
|
narrated += delta;
|
|
290
313
|
onDelta(delta);
|
|
291
314
|
});
|
|
@@ -296,40 +319,31 @@ export async function narrateInteractionResolution(target, session, tool, action
|
|
|
296
319
|
}
|
|
297
320
|
return narrated;
|
|
298
321
|
}
|
|
299
|
-
async function requestCompletion(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
],
|
|
325
|
-
}),
|
|
326
|
-
redirect: 'manual',
|
|
327
|
-
signal: AbortSignal.timeout(30_000),
|
|
328
|
-
};
|
|
329
|
-
const response = injected ? await injected(url.href, init) : await guardedFetch(url, init);
|
|
330
|
-
if (!response.ok)
|
|
331
|
-
throw new Error(`model request failed (${response.status})`);
|
|
332
|
-
return readModelCompletion(response, onContent, MAX_MODEL_RESPONSE);
|
|
322
|
+
async function requestCompletion(binding, messages, target, caller, injected, onContent = () => undefined, extraTools = [], selectedModelTools, maxCompletionTokens, signal) {
|
|
323
|
+
return requestModelCompletion({
|
|
324
|
+
binding,
|
|
325
|
+
messages,
|
|
326
|
+
tools: [
|
|
327
|
+
// On a mixed surface an anonymous caller sees gated tools too: projecting one to the
|
|
328
|
+
// surface is the author's opt-in to advertise it, and dispatch converts the call into a
|
|
329
|
+
// sign-in offer instead of executing (ADR 0201 decision 4). The same selector feeds the
|
|
330
|
+
// compact product guide, keeping instructions and callable tools in lockstep.
|
|
331
|
+
...(selectedModelTools ?? selectTurnModelTools(target, caller)).map((tool) => ({
|
|
332
|
+
type: 'function',
|
|
333
|
+
function: {
|
|
334
|
+
name: tool.name,
|
|
335
|
+
description: tool.description,
|
|
336
|
+
parameters: tool.inputSchema,
|
|
337
|
+
},
|
|
338
|
+
})),
|
|
339
|
+
...extraTools.map((tool) => ({ ...tool, type: 'function' })),
|
|
340
|
+
],
|
|
341
|
+
fetcher: injected ?? ((url, init) => guardedFetch(new URL(url), init)),
|
|
342
|
+
onContent,
|
|
343
|
+
maxResponseBytes: MAX_MODEL_RESPONSE,
|
|
344
|
+
...(maxCompletionTokens === undefined ? {} : { maxCompletionTokens }),
|
|
345
|
+
...(signal === undefined ? {} : { signal }),
|
|
346
|
+
});
|
|
333
347
|
}
|
|
334
348
|
function selectTurnModelTools(target, caller) {
|
|
335
349
|
const assistant = target.served.artifact.server.assistant;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { assistantCustomerIssuer, completeElevation, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
|
+
import { tenantMcpUrl } from '@noodle-borg/module';
|
|
3
4
|
import { readJsonBody, sendForbidden, sendJson } from '../http-util.js';
|
|
4
|
-
import { tenantMcpUrl } from '../registry-helpers.js';
|
|
5
5
|
import { now } from './assistant-route-http.js';
|
|
6
6
|
import { authorizeControlPlane } from './control-plane.js';
|
|
7
7
|
/**
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { resolveManagedVariablesInString } from '../managed-config-expressions.js';
|
|
2
|
+
import { resolveConfigScope } from '../store.js';
|
|
3
|
+
export async function resolveAssistantModelBinding(target, tenant, deploymentId, deps) {
|
|
4
|
+
const declaration = target.served.artifact.server.assistant?.model;
|
|
5
|
+
if (declaration === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (declaration.kind === 'noodle-managed') {
|
|
8
|
+
return deps.managedModelResolver?.resolve({
|
|
9
|
+
tenant,
|
|
10
|
+
deploymentId,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const scope = resolveConfigScope(tenant);
|
|
14
|
+
const [variables, secrets] = await Promise.all([
|
|
15
|
+
deps.registry.configStore.resolveConfigValues('variable', scope),
|
|
16
|
+
deps.registry.configStore.resolveConfigValues('secret', scope),
|
|
17
|
+
]);
|
|
18
|
+
const apiKey = secrets[declaration.apiKey];
|
|
19
|
+
if (!apiKey)
|
|
20
|
+
return undefined;
|
|
21
|
+
return {
|
|
22
|
+
source: 'operator',
|
|
23
|
+
baseUrl: resolveManagedVariablesInString(declaration.baseUrl, variables).replace(/\/$/, ''),
|
|
24
|
+
model: resolveManagedVariablesInString(declaration.model, variables),
|
|
25
|
+
apiKey,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=assistant-model-binding.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { ADMISSION_DEFAULTS, clientAddressBucket } from '@noodle-borg/admission-limits/portable';
|
|
3
3
|
import { mintPublicSession, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
|
|
4
|
+
import { assistantSessionUsageRequestEvent, captureAssistantUsage, } from '@noodle-borg/observability';
|
|
4
5
|
import { assistantSessionResponseSchema } from '@noodle-borg/wire-contracts';
|
|
5
6
|
import { readJsonBody, sendJson } from '../http-util.js';
|
|
6
7
|
import { assistantBrowserConfiguration } from './assistant-configuration.js';
|
|
@@ -20,6 +21,7 @@ function tenantOf(embed) {
|
|
|
20
21
|
return { org: embed.org, app: embed.app, env: embed.env };
|
|
21
22
|
}
|
|
22
23
|
export async function handlePublicAssistantSession(req, res, deps) {
|
|
24
|
+
const usageStartedAt = performance.now();
|
|
23
25
|
const embeds = deps.publicEmbeds;
|
|
24
26
|
const counters = deps.admissionCounters;
|
|
25
27
|
if (!embeds || !counters) {
|
|
@@ -37,6 +39,8 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
37
39
|
// applies — losing fairness, never solvency.
|
|
38
40
|
const addressBucket = clientAddressBucket(typeof req.headers['x-forwarded-for'] === 'string' ? req.headers['x-forwarded-for'] : undefined);
|
|
39
41
|
let configuration;
|
|
42
|
+
let sponsoredBudget;
|
|
43
|
+
let usageSession;
|
|
40
44
|
const result = await mintPublicSession({ embedId, origin, ...(addressBucket ? { addressBucket } : {}) }, deps.admissionEnvelope ?? ADMISSION_DEFAULTS, {
|
|
41
45
|
embeds,
|
|
42
46
|
counters,
|
|
@@ -44,10 +48,17 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
44
48
|
const target = await deps.registry.getActiveByTenant(tenantOf(embed));
|
|
45
49
|
if (!target?.deploymentId)
|
|
46
50
|
return undefined;
|
|
51
|
+
if (target.served.artifact.server.assistant?.model.kind === 'noodle-managed') {
|
|
52
|
+
sponsoredBudget = (await deps.managedModelResolver?.resolve({
|
|
53
|
+
tenant: tenantOf(embed),
|
|
54
|
+
deploymentId: target.deploymentId,
|
|
55
|
+
}))?.publicAdmission;
|
|
56
|
+
}
|
|
47
57
|
// Read from the ACTIVE deployment rather than cached on the embed record — which is what lets
|
|
48
58
|
// `noodle deploy` alone update a snippet a customer pasted months ago.
|
|
49
59
|
return publicSurfaceOf(target.served.artifact.server.assistant);
|
|
50
60
|
},
|
|
61
|
+
resolveBudgetBounds: async () => sponsoredBudget,
|
|
51
62
|
createSession: async (input) => {
|
|
52
63
|
const target = await deps.registry.getActiveByTenant(tenantOf(input.embed));
|
|
53
64
|
if (!target?.deploymentId)
|
|
@@ -60,6 +71,9 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
60
71
|
clientId: input.embed.embedId,
|
|
61
72
|
tenant: tenantOf(input.embed),
|
|
62
73
|
deploymentId: target.deploymentId,
|
|
74
|
+
modelSource: target.served.artifact.server.assistant?.model.kind === 'noodle-managed'
|
|
75
|
+
? 'noodle-managed'
|
|
76
|
+
: 'operator',
|
|
63
77
|
origin: input.origin,
|
|
64
78
|
publicEmbedId: input.embed.embedId,
|
|
65
79
|
caller: { subject: input.subject, identityKind: 'anonymous' },
|
|
@@ -68,6 +82,7 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
68
82
|
expiresAt: new Date(current.getTime() + input.envelope.sessionIdleMs).toISOString(),
|
|
69
83
|
absoluteExpiresAt: new Date(current.getTime() + input.envelope.sessionAbsoluteMs).toISOString(),
|
|
70
84
|
});
|
|
85
|
+
usageSession = created.session;
|
|
71
86
|
return { token: created.token, expiresAt: created.session.expiresAt };
|
|
72
87
|
},
|
|
73
88
|
// Opaque, server-minted, unlinkable to any person: nothing from the request becomes a subject.
|
|
@@ -95,6 +110,9 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
95
110
|
// Parse rather than trust: every published @noodleseed/assistant widget consumes this shape, and the
|
|
96
111
|
// public mint must not be the path that quietly diverges from it (ADR 0151).
|
|
97
112
|
assistantSessionResponseSchema.parse(sessionBody);
|
|
113
|
+
if (usageSession !== undefined) {
|
|
114
|
+
captureAssistantUsage(deps.captureRequestEvent, assistantSessionUsageRequestEvent(usageSession, performance.now() - usageStartedAt));
|
|
115
|
+
}
|
|
98
116
|
return sendJson(res, 201, sessionBody);
|
|
99
117
|
}
|
|
100
118
|
//# sourceMappingURL=assistant-public-session.js.map
|
|
@@ -21,6 +21,12 @@ addressBucket) {
|
|
|
21
21
|
const result = await admitPublicTurn({ sessionId: session.id, publicEmbedId, message, ...(addressBucket ? { addressBucket } : {}) }, deps.admissionEnvelope ?? ADMISSION_DEFAULTS, {
|
|
22
22
|
counters,
|
|
23
23
|
embeds,
|
|
24
|
+
resolveBudgetBounds: async () => session.modelSource !== 'noodle-managed'
|
|
25
|
+
? undefined
|
|
26
|
+
: (await deps.managedModelResolver?.resolve({
|
|
27
|
+
tenant: session.tenant,
|
|
28
|
+
deploymentId: session.deploymentId,
|
|
29
|
+
}))?.publicAdmission,
|
|
24
30
|
consumeTurn: (id, limit) => deps.store.consumeTurn(id, limit),
|
|
25
31
|
now: () => now(deps),
|
|
26
32
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ASSISTANT_USAGE_METHOD, aggregateAssistantUsage } from '@noodle-borg/observability';
|
|
2
|
+
import { normalizedTimestamps, sendJson } from '../http-util.js';
|
|
3
|
+
import { authorizeTenantControl, developerGrantRouteAccess } from './control-plane.js';
|
|
4
|
+
const WINDOWS_MS = new Map([
|
|
5
|
+
['24h', 24 * 60 * 60 * 1000],
|
|
6
|
+
['7d', 7 * 24 * 60 * 60 * 1000],
|
|
7
|
+
['30d', 30 * 24 * 60 * 60 * 1000],
|
|
8
|
+
]);
|
|
9
|
+
/** Bounded raw-event scan; a filled scan is explicitly marked partial in the response. */
|
|
10
|
+
const ASSISTANT_USAGE_SCAN_LIMIT = 50_000;
|
|
11
|
+
export async function handleTenantAssistantUsage(req, res, gate, controlPlane, events, ref, url, developerGrants) {
|
|
12
|
+
const identity = await authorizeTenantControl(req, res, gate, controlPlane, ref.org, developerGrantRouteAccess(developerGrants, 'cloud:read'));
|
|
13
|
+
if (identity === false)
|
|
14
|
+
return;
|
|
15
|
+
const windowParam = url.searchParams.get('window') ?? '7d';
|
|
16
|
+
const windowMs = WINDOWS_MS.get(windowParam);
|
|
17
|
+
if (windowMs === undefined) {
|
|
18
|
+
return sendJson(res, 400, { error: '"window" must be one of 24h, 7d, 30d' });
|
|
19
|
+
}
|
|
20
|
+
const timestamps = normalizedTimestamps(res, {
|
|
21
|
+
until: url.searchParams.get('until') ?? undefined,
|
|
22
|
+
});
|
|
23
|
+
if (timestamps === undefined)
|
|
24
|
+
return;
|
|
25
|
+
const since = new Date(Date.now() - windowMs).toISOString();
|
|
26
|
+
const window = await events.list({
|
|
27
|
+
org: ref.org,
|
|
28
|
+
app: ref.app,
|
|
29
|
+
env: ref.env,
|
|
30
|
+
method: ASSISTANT_USAGE_METHOD,
|
|
31
|
+
since,
|
|
32
|
+
...(timestamps.until === undefined ? {} : { until: timestamps.until }),
|
|
33
|
+
limit: ASSISTANT_USAGE_SCAN_LIMIT,
|
|
34
|
+
});
|
|
35
|
+
return sendJson(res, 200, {
|
|
36
|
+
ok: true,
|
|
37
|
+
window: { since, ...(timestamps.until === undefined ? {} : { until: timestamps.until }) },
|
|
38
|
+
...(window.length >= ASSISTANT_USAGE_SCAN_LIMIT ? { truncated: true } : {}),
|
|
39
|
+
metrics: aggregateAssistantUsage(window),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=assistant-usage.js.map
|
|
@@ -2,13 +2,14 @@ import { clientAddressBucket, } from '@noodle-borg/admission-limits/portable';
|
|
|
2
2
|
import { ASSISTANT_SESSION_IDLE_MS, dispatchAssistantTool, parseAssistantContextPreferences, parseAssistantCustomerRouting, resumeTurnMessage, shouldAutoResume, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
3
|
import { canonicalizeAuthorizationClaimValues } from '@noodle-borg/auth';
|
|
4
4
|
import { validateJsonSchemaWithDefaults } from '@noodle-borg/compiler';
|
|
5
|
+
import { tenantMcpUrl } from '@noodle-borg/module';
|
|
6
|
+
import { assistantRefusedTurnUsageRequestEvent, assistantSessionUsageRequestEvent, assistantTurnNumber, assistantTurnUsageRequestEvent, captureAssistantUsage, } from '@noodle-borg/observability';
|
|
5
7
|
import { evaluateToolAuthorization, mapExecutionError, mapResourceContents, mapResourcesList, mapToolOutput, mapToolsList, } from '@noodle-borg/protocol';
|
|
6
8
|
import { executeResource } from '@noodle-borg/runtime';
|
|
7
9
|
import { assistantMessageTurnRequestSchema, assistantResumeTurnRequestSchema, assistantSessionResponseSchema, } from '@noodle-borg/wire-contracts';
|
|
8
10
|
import { readJsonBody, sendForbidden, sendJson, sendUnauthorized } from '../http-util.js';
|
|
9
11
|
import { resolveInvocationContextSnapshot } from '../invocation-context.js';
|
|
10
|
-
import {
|
|
11
|
-
import { runAgentTurn } from './assistant-agent.js';
|
|
12
|
+
import { createAssistantTurnStats, runAgentTurn } from './assistant-agent.js';
|
|
12
13
|
import { assistantBrowserConfiguration } from './assistant-configuration.js';
|
|
13
14
|
import { elevateAssistantSession } from './assistant-elevation.js';
|
|
14
15
|
import { executeAssistantKnowledgeSearch, findAssistantKnowledgeComponent, resolveAssistantKnowledge, } from './assistant-knowledge.js';
|
|
@@ -144,6 +145,7 @@ export async function handleAssistantClients(req, res, tenant, id, action, deps)
|
|
|
144
145
|
sendJson(res, 405, { error: 'method not allowed' });
|
|
145
146
|
}
|
|
146
147
|
export async function handleAssistantSession(req, res, deps) {
|
|
148
|
+
const usageStartedAt = performance.now();
|
|
147
149
|
const credentials = basicCredentials(req);
|
|
148
150
|
if (!credentials)
|
|
149
151
|
return sendUnauthorized(res, 'invalid assistant client');
|
|
@@ -245,6 +247,7 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
245
247
|
clientId: client.id,
|
|
246
248
|
tenant: client.tenant,
|
|
247
249
|
deploymentId: target.deploymentId,
|
|
250
|
+
modelSource: assistant.model.kind === 'noodle-managed' ? 'noodle-managed' : 'operator',
|
|
248
251
|
origin: parsed.origin,
|
|
249
252
|
...(customerRouting.customerRouting
|
|
250
253
|
? { customerRouting: customerRouting.customerRouting }
|
|
@@ -272,12 +275,15 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
272
275
|
// Every published @noodleseed/assistant widget consumes this shape; parse (don't just test)
|
|
273
276
|
// so a contract break fails loudly at mint time instead of stranding deployed widgets (ADR 0151).
|
|
274
277
|
assistantSessionResponseSchema.parse(sessionBody);
|
|
278
|
+
captureAssistantUsage(deps.captureRequestEvent, assistantSessionUsageRequestEvent(session.session, performance.now() - usageStartedAt));
|
|
275
279
|
return sendJson(res, 201, sessionBody);
|
|
276
280
|
}
|
|
277
281
|
export async function handleAssistantTurn(req, res, deps) {
|
|
282
|
+
const usageStartedAt = performance.now();
|
|
278
283
|
const session = await authenticateSession(req, res, deps);
|
|
279
284
|
if (!session)
|
|
280
285
|
return;
|
|
286
|
+
const usageTurnNumber = assistantTurnNumber(session);
|
|
281
287
|
applyBrowserCors(req, res, session.origin);
|
|
282
288
|
const body = await readJsonBody(req, deps.maxBody);
|
|
283
289
|
if (!body.ok)
|
|
@@ -296,6 +302,7 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
296
302
|
}
|
|
297
303
|
const refusal = await refusePublicTurn(deps, session, 'resume', addressBucket);
|
|
298
304
|
if (refusal) {
|
|
305
|
+
captureAssistantUsage(deps.captureRequestEvent, assistantRefusedTurnUsageRequestEvent(session, refusal.code, performance.now() - usageStartedAt));
|
|
299
306
|
return sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
|
|
300
307
|
}
|
|
301
308
|
const pending = await deps.store.consumePendingResume(session.id);
|
|
@@ -344,6 +351,7 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
344
351
|
// of the model, so a refused turn costs the customer nothing.
|
|
345
352
|
const refusal = await refusePublicTurn(deps, session, message, addressBucket);
|
|
346
353
|
if (refusal) {
|
|
354
|
+
captureAssistantUsage(deps.captureRequestEvent, assistantRefusedTurnUsageRequestEvent(session, refusal.code, performance.now() - usageStartedAt));
|
|
347
355
|
return sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
|
|
348
356
|
}
|
|
349
357
|
// The visitor typed first: any pending post-sign-in resume is moot, and must not fire later.
|
|
@@ -376,12 +384,13 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
376
384
|
});
|
|
377
385
|
res.flushHeaders?.();
|
|
378
386
|
const events = [];
|
|
387
|
+
const stats = createAssistantTurnStats();
|
|
379
388
|
const emit = (event) => {
|
|
380
389
|
events.push(event);
|
|
381
390
|
res.write(`event: ${event.event}\ndata: ${JSON.stringify(event.data)}\n\n`);
|
|
382
391
|
};
|
|
383
392
|
try {
|
|
384
|
-
await runAgentTurn(target, session, message.trim(), invocationContext, deps, emit, turn?.modelContext, turn?.pageContext);
|
|
393
|
+
await runAgentTurn(target, session, message.trim(), invocationContext, deps, emit, turn?.modelContext, turn?.pageContext, stats);
|
|
385
394
|
const assistantContent = events
|
|
386
395
|
.filter((event) => event.event === 'content')
|
|
387
396
|
.map((event) => String(event.data.delta ?? ''))
|
|
@@ -395,6 +404,14 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
395
404
|
emit({ event: 'error', data: { code: 'model_transport_failed', retryable: true } });
|
|
396
405
|
}
|
|
397
406
|
res.end('event: done\ndata: {}\n\n');
|
|
407
|
+
const error = events.find((event) => event.event === 'error');
|
|
408
|
+
captureAssistantUsage(deps.captureRequestEvent, assistantTurnUsageRequestEvent(session, {
|
|
409
|
+
outcome: error === undefined ? 'delivered' : 'failed',
|
|
410
|
+
counters: stats,
|
|
411
|
+
turnNumber: usageTurnNumber,
|
|
412
|
+
durationMs: performance.now() - usageStartedAt,
|
|
413
|
+
...(typeof error?.data.code === 'string' ? { errorKind: error.data.code } : {}),
|
|
414
|
+
}));
|
|
398
415
|
}
|
|
399
416
|
/** Standard MCP operations exposed to an initialized app inside the embedded assistant host. */
|
|
400
417
|
export async function handleAssistantAppRequest(req, res, deps) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { bearerToken } from '@noodle-borg/control-plane/portable';
|
|
2
|
-
import { normalizeServerVersion } from '@noodle-borg/module';
|
|
2
|
+
import { normalizeServerVersion, tenantMcpUrl } from '@noodle-borg/module';
|
|
3
3
|
import { freezeCustomerRoutes, resolveCustomerRouteBinding, } from '@noodle-borg/runtime';
|
|
4
4
|
import { sendJson } from '../http-util.js';
|
|
5
|
-
import { tenantMcpUrl } from '../registry-helpers.js';
|
|
6
5
|
/** Verify one real customer identity and probe delegated bindings without calling business APIs. */
|
|
7
6
|
export async function handleLiveAuthDoctor(req, res, registry, tenant, options) {
|
|
8
7
|
let serverVersion;
|