@noodleseed/one 0.148.0 → 0.150.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 +34 -0
- package/dist/commands/assistant-ops.js.map +1 -1
- package/dist/commands/assistant-surface-ops.d.ts +17 -1
- package/dist/commands/assistant-surface-ops.d.ts.map +1 -1
- package/dist/commands/assistant-surface-ops.js +38 -2
- package/dist/commands/assistant-surface-ops.js.map +1 -1
- package/dist/commands/intents-ops.js +1 -1
- package/dist/commands/intents-ops.js.map +1 -1
- package/dist/dev-local-runtime.d.ts +1 -0
- package/dist/dev-local-runtime.d.ts.map +1 -1
- package/dist/dev-local-runtime.js +3 -0
- package/dist/dev-local-runtime.js.map +1 -1
- package/node_modules/@noodle-borg/admission-limits/dist/counter-store.d.ts +20 -0
- package/node_modules/@noodle-borg/admission-limits/dist/counter-store.js +12 -0
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.d.ts +34 -2
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +45 -15
- package/node_modules/@noodle-borg/admission-limits/dist/in-memory-counter-store.d.ts +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/in-memory-counter-store.js +14 -1
- package/node_modules/@noodle-borg/admission-limits/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/visitor-bucket.d.ts +20 -0
- package/node_modules/@noodle-borg/admission-limits/dist/visitor-bucket.js +30 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-tool-design-ref.js +4 -0
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/app-tool-call.d.ts +42 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/app-tool-call.js +45 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.d.ts +2 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.js +17 -3
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-context.d.ts +13 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-context.js +53 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.d.ts +12 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.js +15 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-operator-view.d.ts +79 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-operator-view.js +67 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +37 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.js +37 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.js +12 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/managed-spend.d.ts +80 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/managed-spend.js +186 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +16 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.d.ts +14 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.js +23 -6
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.js +22 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +15 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +24 -0
- package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +31 -3
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +3 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +29 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-observability.js +1 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +12 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +8 -3
- package/node_modules/@noodle-borg/module/dist/request-analytics.d.ts +19 -2
- package/node_modules/@noodle-borg/module/dist/request-analytics.js +15 -2
- package/node_modules/@noodle-borg/observability/dist/assistant-app-tool-usage.js +25 -0
- package/node_modules/@noodle-borg/observability/dist/assistant-usage.js +68 -10
- package/node_modules/@noodle-borg/observability/dist/index.js +2 -0
- package/node_modules/@noodle-borg/observability/dist/request-event-query.js +48 -0
- package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +13 -2
- package/node_modules/@noodle-borg/observability/dist/request-events.js +3 -0
- package/node_modules/@noodle-borg/service/dist/invocation-context.js +1 -53
- package/node_modules/@noodle-borg/service/dist/observability-runtime.js +4 -6
- package/node_modules/@noodle-borg/service/dist/routes/analytics.js +5 -29
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +57 -29
- package/node_modules/@noodle-borg/service/dist/routes/assistant-embeds.js +26 -38
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-resolution.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +1 -4
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-configuration.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +8 -4
- package/node_modules/@noodle-borg/service/dist/routes/assistant-suggestions.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +64 -49
- package/node_modules/@noodle-borg/service/dist/routes/intent-capture.js +0 -12
- package/node_modules/@noodle-borg/service/dist/service.js +2 -3
- package/node_modules/@noodle-borg/service/package.json +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/request-capture.js +2 -0
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ADMISSION_DEFAULTS } from '@noodle-borg/admission-limits/portable';
|
|
2
2
|
import { assistantCoreModelMessages, assistantTurnModelContextMessages, requestAssistantSuggestedPrompts, requestModelCompletion, resolveAssistantContextProviderModelResult, } from '@noodle-borg/assistant-gateway/model-runtime';
|
|
3
|
-
import { assistantGuideModelContext, assistantModelToolOncePerSession, assistantModelToolRequiredWhenVisible, assistantViewAvailableData, dispatchAssistantTool, projectAssistantGuide, publicSurfaceOf, selectAssistantModelTools, toolTouchesDelegatedAuth, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
|
+
import { assistantGuideModelContext, assistantModelToolOncePerSession, assistantModelToolRequiredWhenVisible, assistantOmittedToolResult, assistantViewAvailableData, dispatchAssistantTool, projectAssistantGuide, publicSurfaceOf, selectAssistantModelTools, toolTouchesDelegatedAuth, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
4
4
|
import { validateJsonSchemaWithDefaults } from '@noodle-borg/compiler';
|
|
5
5
|
import { guardedFetch } from '@noodle-borg/connector-http';
|
|
6
6
|
import { evaluateToolAuthorization } from '@noodle-borg/protocol';
|
|
@@ -8,6 +8,19 @@ import { interceptForElevation, offersSignIn } from './assistant-elevation.js';
|
|
|
8
8
|
import { assistantKnowledgeModelTools, executeAssistantKnowledgeSearch, findAssistantKnowledgeComponent, KNOWLEDGE_CITATION_GUIDANCE, resolveAssistantKnowledge, } from './assistant-knowledge.js';
|
|
9
9
|
import { resolveAssistantModelBinding } from './assistant-model-binding.js';
|
|
10
10
|
const MAX_MODEL_RESPONSE = 1 << 20;
|
|
11
|
+
/**
|
|
12
|
+
* The agent-loop bounds come from the admission envelope so an operator lowering them changes
|
|
13
|
+
* behaviour. They used to be a constant here that happened to equal the envelope's default, which
|
|
14
|
+
* meant the envelope documented a limit it did not control.
|
|
15
|
+
*/
|
|
16
|
+
function agentBounds(deps, policy) {
|
|
17
|
+
const { modelStepsPerTurn, toolCallsPerTurn } = deps.admissionEnvelope ?? ADMISSION_DEFAULTS;
|
|
18
|
+
// A per-tenant policy narrows a turn; it can never widen one, so the envelope wins when lower.
|
|
19
|
+
return {
|
|
20
|
+
steps: Math.min(modelStepsPerTurn, policy?.maxModelStepsPerTurn ?? modelStepsPerTurn),
|
|
21
|
+
toolCalls: Math.min(toolCallsPerTurn, policy?.maxToolCallsPerTurn ?? toolCallsPerTurn),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
11
24
|
export function createAssistantTurnStats() {
|
|
12
25
|
return {
|
|
13
26
|
modelRequests: 0,
|
|
@@ -21,9 +34,12 @@ export function createAssistantTurnStats() {
|
|
|
21
34
|
}
|
|
22
35
|
/** Run one model/tool loop against the immutable invocation-context snapshot for this turn. */
|
|
23
36
|
export async function runAgentTurn(target, session, message, context, deps, emit, modelContext, pageContext, stats = createAssistantTurnStats(), suggestions = false) {
|
|
37
|
+
// Every terminal failure in this loop is one shape: a code, and the turn ends. Naming it keeps the
|
|
38
|
+
// dozen sites readable and stops a new one inventing a different envelope.
|
|
39
|
+
const fail = (code) => emit({ event: 'error', data: { code } });
|
|
24
40
|
const assistant = target.served.artifact.server.assistant;
|
|
25
41
|
if (!assistant)
|
|
26
|
-
return
|
|
42
|
+
return fail('assistant_unavailable');
|
|
27
43
|
const binding = await resolveAssistantModelBinding(target, session.tenant, session.deploymentId, deps);
|
|
28
44
|
if (!binding) {
|
|
29
45
|
return emit({
|
|
@@ -35,12 +51,18 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
35
51
|
},
|
|
36
52
|
});
|
|
37
53
|
}
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
// Zero means offer none, from every source — not "call one and meet `tool_call_budget_exhausted`".
|
|
55
|
+
// A required tool and a knowledge tool each reach the model by their own path, so silencing only
|
|
56
|
+
// the ordinary ones would still advertise a tool the turn can never run.
|
|
57
|
+
const bounds = agentBounds(deps, binding.requestPolicy);
|
|
58
|
+
const toolless = bounds.toolCalls === 0;
|
|
59
|
+
const knowledge = toolless ? undefined : await resolveAssistantKnowledge(target.served);
|
|
60
|
+
let modelTools = toolless
|
|
61
|
+
? []
|
|
62
|
+
: selectTurnModelTools(target, session.caller, message, session.modelToolUses);
|
|
40
63
|
const requiredTools = modelTools.filter(assistantModelToolRequiredWhenVisible);
|
|
41
|
-
if (requiredTools.length > 1)
|
|
42
|
-
return
|
|
43
|
-
}
|
|
64
|
+
if (requiredTools.length > 1)
|
|
65
|
+
return fail('multiple_required_model_tools');
|
|
44
66
|
let requiredTool = requiredTools[0];
|
|
45
67
|
const guideProjection = projectAssistantGuide({
|
|
46
68
|
appPackage: target.served.appPackageSnapshot?.artifact,
|
|
@@ -73,19 +95,17 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
73
95
|
...session.history,
|
|
74
96
|
{ role: 'user', content: message },
|
|
75
97
|
];
|
|
76
|
-
const envelope = deps.admissionEnvelope ?? ADMISSION_DEFAULTS;
|
|
77
|
-
const bounds = { steps: envelope.modelStepsPerTurn, toolCalls: envelope.toolCallsPerTurn };
|
|
78
98
|
// Across every step of this turn, not per step: eight tool calls is eight, however the model splits
|
|
79
99
|
// them, or a model that loops one call per step would spend the budget a step at a time.
|
|
80
100
|
let toolCallsThisTurn = 0;
|
|
101
|
+
let omittedToolRecoveries = 0;
|
|
81
102
|
let remainingTokens = binding.requestPolicy?.maxTokensPerTurn;
|
|
82
103
|
const turnSignal = binding.requestPolicy?.maxTurnMs === undefined
|
|
83
104
|
? undefined
|
|
84
105
|
: AbortSignal.timeout(binding.requestPolicy.maxTurnMs);
|
|
85
106
|
for (let step = 0; step < bounds.steps; step += 1) {
|
|
86
|
-
if (remainingTokens !== undefined && remainingTokens <= 0)
|
|
87
|
-
return
|
|
88
|
-
}
|
|
107
|
+
if (remainingTokens !== undefined && remainingTokens <= 0)
|
|
108
|
+
return fail('model_token_budget_exhausted');
|
|
89
109
|
const requestTokenLimit = Math.min(binding.requestPolicy?.maxCompletionTokens ?? Number.MAX_SAFE_INTEGER, remainingTokens ?? Number.MAX_SAFE_INTEGER);
|
|
90
110
|
const requiredToolForStep = requiredTool;
|
|
91
111
|
const stepModelTools = requiredToolForStep === undefined ? modelTools : [requiredToolForStep];
|
|
@@ -106,12 +126,11 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
106
126
|
}
|
|
107
127
|
const response = completion.choices[0]?.message;
|
|
108
128
|
if (!response)
|
|
109
|
-
return
|
|
129
|
+
return fail('invalid_model_response');
|
|
110
130
|
if (requiredToolForStep !== undefined &&
|
|
111
131
|
(response.tool_calls?.length !== 1 ||
|
|
112
|
-
response.tool_calls[0]?.function.name !== requiredToolForStep.name))
|
|
113
|
-
return
|
|
114
|
-
}
|
|
132
|
+
response.tool_calls[0]?.function.name !== requiredToolForStep.name))
|
|
133
|
+
return fail('required_model_tool_missing');
|
|
115
134
|
if (!response.tool_calls?.length) {
|
|
116
135
|
const assistantContent = response.content || streamedContent;
|
|
117
136
|
if (assistantContent)
|
|
@@ -141,9 +160,8 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
141
160
|
for (const call of response.tool_calls) {
|
|
142
161
|
toolCallsThisTurn += 1;
|
|
143
162
|
stats.toolCalls += 1;
|
|
144
|
-
if (toolCallsThisTurn > bounds.toolCalls)
|
|
145
|
-
return
|
|
146
|
-
}
|
|
163
|
+
if (toolCallsThisTurn > bounds.toolCalls)
|
|
164
|
+
return fail('tool_call_budget_exhausted');
|
|
147
165
|
const knowledgeComponent = findAssistantKnowledgeComponent(knowledge, call.function.name);
|
|
148
166
|
if (knowledge !== undefined && knowledgeComponent !== undefined) {
|
|
149
167
|
let knowledgeArgs;
|
|
@@ -151,7 +169,7 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
151
169
|
knowledgeArgs = JSON.parse(call.function.arguments);
|
|
152
170
|
}
|
|
153
171
|
catch {
|
|
154
|
-
return
|
|
172
|
+
return fail('invalid_tool_arguments');
|
|
155
173
|
}
|
|
156
174
|
emit({ event: 'tool_started', data: { id: call.id, tool: call.function.name } });
|
|
157
175
|
messages.push({
|
|
@@ -163,7 +181,19 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
163
181
|
}
|
|
164
182
|
const tool = stepModelTools.find((candidate) => candidate.name === call.function.name);
|
|
165
183
|
if (!tool) {
|
|
166
|
-
|
|
184
|
+
// Tell the model rather than ending the turn on a bare error; `assistantOmittedToolResult`
|
|
185
|
+
// carries the reasoning. Once per turn: a model that insists after being told is the
|
|
186
|
+
// protocol violation the error code exists for, and it stops paying for extra steps.
|
|
187
|
+
if (omittedToolRecoveries > 0)
|
|
188
|
+
return fail('invalid_model_tool_call');
|
|
189
|
+
omittedToolRecoveries += 1;
|
|
190
|
+
modelTools = [];
|
|
191
|
+
messages.push({
|
|
192
|
+
role: 'tool',
|
|
193
|
+
tool_call_id: call.id,
|
|
194
|
+
content: assistantOmittedToolResult(call.function.name),
|
|
195
|
+
});
|
|
196
|
+
continue;
|
|
167
197
|
}
|
|
168
198
|
// A mixed surface turns "denied" into "sign in first". The intercept runs BEFORE the
|
|
169
199
|
// authorization denial — an ADR 0185-gated tool is exactly what an anonymous visitor signs
|
|
@@ -195,22 +225,20 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
195
225
|
});
|
|
196
226
|
continue;
|
|
197
227
|
}
|
|
198
|
-
if (!evaluateToolAuthorization(tool.authorization, session.caller).allow)
|
|
199
|
-
return
|
|
200
|
-
}
|
|
228
|
+
if (!evaluateToolAuthorization(tool.authorization, session.caller).allow)
|
|
229
|
+
return fail('invalid_model_tool_call');
|
|
201
230
|
let args;
|
|
202
231
|
try {
|
|
203
232
|
args = JSON.parse(call.function.arguments);
|
|
204
233
|
}
|
|
205
234
|
catch {
|
|
206
|
-
return
|
|
235
|
+
return fail('invalid_tool_arguments');
|
|
207
236
|
}
|
|
208
237
|
// Validate AND apply schema defaults; the pending record and the execution below both use
|
|
209
238
|
// the coerced copy, so the confirmed call equals the executed call (roadmap S5).
|
|
210
239
|
const coerced = validateJsonSchemaWithDefaults(tool.inputSchema, args);
|
|
211
|
-
if (coerced.issues.length > 0)
|
|
212
|
-
return
|
|
213
|
-
}
|
|
240
|
+
if (coerced.issues.length > 0)
|
|
241
|
+
return fail('invalid_tool_arguments');
|
|
214
242
|
args = coerced.value;
|
|
215
243
|
const dispatch = await dispatchAssistantTool({
|
|
216
244
|
artifact: target.served.artifact,
|
|
@@ -250,7 +278,7 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
250
278
|
});
|
|
251
279
|
}
|
|
252
280
|
}
|
|
253
|
-
|
|
281
|
+
fail('step_limit');
|
|
254
282
|
}
|
|
255
283
|
/** Generate an initial prompt set from the same exact surface/context assembly as an ordinary turn. */
|
|
256
284
|
export async function generateInitialAssistantSuggestions(target, session, context, deps, modelContext, pageContext) {
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { ADMISSION_DEFAULTS, clamp } from '@noodle-borg/admission-limits/portable';
|
|
2
|
-
import { publicSurfaceOf, surfaceEnvelope } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { assistantEmbedOperatorView, parsePublicEmbedBudget, publicSurfaceOf, surfaceEnvelope, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
3
|
import { readJsonBody, sendForbidden, sendJson } from '../http-util.js';
|
|
4
4
|
import { now } from './assistant-route-http.js';
|
|
5
5
|
import { authorizeControlPlane } from './control-plane.js';
|
|
6
|
+
/**
|
|
7
|
+
* The operator's view of a tenant's public surfaces, and the one control over them.
|
|
8
|
+
*
|
|
9
|
+
* `PATCH` sets the daily caps; `--turns-per-day 0` is the kill switch, and it is deliberately the only
|
|
10
|
+
* documented one. Revoking an embed would also stop a surface, but it destroys the paste-once id and
|
|
11
|
+
* every page carrying it, so it stays an exceptional action rather than the routine off-switch.
|
|
12
|
+
*
|
|
13
|
+
* Origins, capabilities, and surfaceMode are read from the *active* deployment rather than the embed
|
|
14
|
+
* row, so what an operator sees here is what a browser will actually be held to at mint time (the row
|
|
15
|
+
* keeps its provisioning-time mode by design — `ensure` never overwrites a live row).
|
|
16
|
+
*/
|
|
6
17
|
export async function handleAssistantEmbeds(req, res, tenant, embedId, deps) {
|
|
7
18
|
const identity = await authorizeControlPlane(req, res, deps.gate, { requireIdentity: false });
|
|
8
19
|
if (identity === false)
|
|
@@ -30,7 +41,7 @@ export async function handleAssistantEmbeds(req, res, tenant, embedId, deps) {
|
|
|
30
41
|
const body = await readJsonBody(req, deps.maxBody);
|
|
31
42
|
if (!body.ok)
|
|
32
43
|
return sendJson(res, body.status, { error: body.error });
|
|
33
|
-
const budget =
|
|
44
|
+
const budget = parsePublicEmbedBudget(body.value);
|
|
34
45
|
if (!budget.ok)
|
|
35
46
|
return sendJson(res, 400, { error: budget.error });
|
|
36
47
|
const updated = await embeds.setBudget(embedId, budget.value, now(deps));
|
|
@@ -46,44 +57,21 @@ export async function handleAssistantEmbeds(req, res, tenant, embedId, deps) {
|
|
|
46
57
|
res.setHeader('Allow', embedId === undefined ? 'GET' : 'PATCH');
|
|
47
58
|
return sendJson(res, 405, { error: 'method not allowed' });
|
|
48
59
|
}
|
|
49
|
-
function parseBudget(body) {
|
|
50
|
-
const { turnsPerDay, mintsPerDay } = body;
|
|
51
|
-
const value = {};
|
|
52
|
-
for (const [name, raw] of [
|
|
53
|
-
['turnsPerDay', turnsPerDay],
|
|
54
|
-
['mintsPerDay', mintsPerDay],
|
|
55
|
-
]) {
|
|
56
|
-
if (raw === undefined)
|
|
57
|
-
continue;
|
|
58
|
-
// Zero is valid and is the point; anything negative or fractional is a mistake worth saying out
|
|
59
|
-
// loud rather than silently flooring, because an operator setting a cap is being deliberate.
|
|
60
|
-
if (typeof raw !== 'number' || !Number.isInteger(raw) || raw < 0) {
|
|
61
|
-
return { ok: false, error: `"${name}" must be a non-negative integer` };
|
|
62
|
-
}
|
|
63
|
-
value[name] = raw;
|
|
64
|
-
}
|
|
65
|
-
if (Object.keys(value).length === 0) {
|
|
66
|
-
return { ok: false, error: 'set at least one of "turnsPerDay" or "mintsPerDay"' };
|
|
67
|
-
}
|
|
68
|
-
return { ok: true, value };
|
|
69
|
-
}
|
|
70
60
|
async function view(record, tenant, defaults, deps, counters) {
|
|
71
61
|
const target = await deps.registry.getActiveByTenant(tenant);
|
|
72
|
-
const surface = publicSurfaceOf(target?.served.artifact.server.assistant);
|
|
73
|
-
// Clamped even for the pure-default case, so what an operator reads is what admission will enforce.
|
|
74
|
-
const envelope = surfaceEnvelope(clamp(defaults), record);
|
|
75
62
|
const at = now(deps);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
63
|
+
// The same bounds admission resolves, not the bare defaults: a sponsored surface enforces the
|
|
64
|
+
// hosted envelope, and reporting the unsponsored one told an operator a number nothing honours.
|
|
65
|
+
const deploymentId = target?.deploymentId ?? '';
|
|
66
|
+
const bounds = (await deps.managedModelResolver?.resolve({ tenant, deploymentId }))
|
|
67
|
+
?.publicAdmission;
|
|
68
|
+
return assistantEmbedOperatorView({
|
|
69
|
+
record,
|
|
70
|
+
surface: publicSurfaceOf(target?.served.artifact.server.assistant),
|
|
71
|
+
// Clamped even for the pure-default case, so what an operator reads is what admission enforces.
|
|
72
|
+
envelope: surfaceEnvelope(clamp(defaults), record, bounds),
|
|
73
|
+
peek: (key) => counters.peek(key, at),
|
|
74
|
+
...(bounds?.spend === undefined ? {} : { spend: bounds.spend }),
|
|
75
|
+
});
|
|
88
76
|
}
|
|
89
77
|
//# sourceMappingURL=assistant-embeds.js.map
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { withAssistantSessionExecutionAuthority } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
-
import { resolveInvocationContextSnapshot } from '../invocation-context.js';
|
|
1
|
+
import { resolveInvocationContextSnapshot, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
2
|
import { now } from './assistant.js';
|
|
4
3
|
export function assistantInteractionScope(id, session, instant) {
|
|
5
4
|
return {
|
|
@@ -5,10 +5,7 @@ export async function resolveAssistantModelBinding(target, tenant, deploymentId,
|
|
|
5
5
|
if (declaration === undefined)
|
|
6
6
|
return undefined;
|
|
7
7
|
if (declaration.kind === 'noodle-managed') {
|
|
8
|
-
return deps.managedModelResolver?.resolve({
|
|
9
|
-
tenant,
|
|
10
|
-
deploymentId,
|
|
11
|
-
});
|
|
8
|
+
return deps.managedModelResolver?.resolve({ tenant, deploymentId });
|
|
12
9
|
}
|
|
13
10
|
const scope = resolveConfigScope(tenant);
|
|
14
11
|
const [variables, secrets] = await Promise.all([
|
|
@@ -29,7 +29,7 @@ export async function handlePublicAssistantConfiguration(req, res, embedId, deps
|
|
|
29
29
|
if (target === undefined) {
|
|
30
30
|
return sendJson(res, 409, { error: 'assistant deployment is unavailable' });
|
|
31
31
|
}
|
|
32
|
-
const resolved = await effectiveAssistantBrowserConfiguration(target.served.artifact.server, publicEmbedTenant(authorized.embed), deps.appearance);
|
|
32
|
+
const resolved = await effectiveAssistantBrowserConfiguration(target.served.artifact.server, publicEmbedTenant(authorized.embed), deps.appearance, 'public');
|
|
33
33
|
applyBrowserCors(req, res, origin);
|
|
34
34
|
res.setHeader('cache-control', 'private, no-cache');
|
|
35
35
|
return sendJson(res, 200, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { ADMISSION_DEFAULTS, clientAddressBucket } from '@noodle-borg/admission-limits/portable';
|
|
3
3
|
import { effectiveAssistantBrowserConfiguration, mintPublicSession, publicEmbedTenant, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
|
|
4
|
-
import { assistantSessionUsageRequestEvent, captureAssistantUsage, } from '@noodle-borg/observability';
|
|
4
|
+
import { assistantSessionUsageRequestEvent, captureAssistantUsage, captureRefusedAssistantSession, } from '@noodle-borg/observability';
|
|
5
5
|
import { assistantSessionResponseSchema } from '@noodle-borg/wire-contracts';
|
|
6
6
|
import { readJsonBody, sendJson } from '../http-util.js';
|
|
7
7
|
import { applyBrowserCors, assistantSessionEndpoints, now } from './assistant-route-http.js';
|
|
@@ -25,7 +25,7 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
25
25
|
const body = await readJsonBody(req, deps.maxBody);
|
|
26
26
|
if (!body.ok)
|
|
27
27
|
return sendJson(res, body.status, { error: body.error });
|
|
28
|
-
const { embedId } = body.value;
|
|
28
|
+
const { embedId, visitorId } = body.value;
|
|
29
29
|
// The browser's Origin header is the claim under test. The body never asserts its own origin.
|
|
30
30
|
const origin = typeof req.headers.origin === 'string' ? req.headers.origin : undefined;
|
|
31
31
|
// Admission tier 3. Hashed here so no raw address crosses into the decision, a counter key, or an
|
|
@@ -36,7 +36,7 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
36
36
|
let configuration;
|
|
37
37
|
let sponsoredBudget;
|
|
38
38
|
let usageSession;
|
|
39
|
-
const result = await mintPublicSession({ embedId, origin, ...(addressBucket ? { addressBucket } : {}) }, deps.admissionEnvelope ?? ADMISSION_DEFAULTS, {
|
|
39
|
+
const result = await mintPublicSession({ embedId, visitorId, origin, ...(addressBucket ? { addressBucket } : {}) }, deps.admissionEnvelope ?? ADMISSION_DEFAULTS, {
|
|
40
40
|
embeds,
|
|
41
41
|
counters,
|
|
42
42
|
resolveActiveSurface: async (embed) => {
|
|
@@ -59,7 +59,7 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
59
59
|
if (!target?.deploymentId)
|
|
60
60
|
throw new Error('assistant deployment vanished mid-mint');
|
|
61
61
|
const current = now(deps);
|
|
62
|
-
configuration = (await effectiveAssistantBrowserConfiguration(target.served.artifact.server, publicEmbedTenant(input.embed), deps.appearance)).effective;
|
|
62
|
+
configuration = (await effectiveAssistantBrowserConfiguration(target.served.artifact.server, publicEmbedTenant(input.embed), deps.appearance, 'public')).effective;
|
|
63
63
|
const created = await deps.store.createSession({
|
|
64
64
|
// A public surface has no client credential, so the embed id stands in as the minting
|
|
65
65
|
// authority. It is not a secret and nothing downstream may begin treating it as one.
|
|
@@ -86,6 +86,10 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
86
86
|
now: () => now(deps),
|
|
87
87
|
});
|
|
88
88
|
if (!result.ok) {
|
|
89
|
+
// A refusal the surface owns is usage. Without it an operator whose visitors are being turned
|
|
90
|
+
// away sees a healthy-looking page; which refusals count is the helper's decision, not this one's.
|
|
91
|
+
const refusedIn = performance.now() - usageStartedAt;
|
|
92
|
+
captureRefusedAssistantSession(deps.captureRequestEvent, result, refusedIn);
|
|
89
93
|
return sendJson(res, result.status, { error: result.message, code: result.code });
|
|
90
94
|
}
|
|
91
95
|
// Only here, with the origin proven against the live surface, may the page read the response.
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { ADMISSION_DEFAULTS } from '@noodle-borg/admission-limits/portable';
|
|
2
|
-
import { parseAssistantContextPreferences, surfaceEnvelope, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { parseAssistantContextPreferences, resolveInvocationContextSnapshot, surfaceEnvelope, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
3
|
import { assistantSuggestionsRequestSchema } from '@noodle-borg/wire-contracts';
|
|
4
4
|
import { readJsonBody, sendJson } from '../http-util.js';
|
|
5
|
-
import { resolveInvocationContextSnapshot } from '../invocation-context.js';
|
|
6
5
|
import { generateInitialAssistantSuggestions } from './assistant-agent.js';
|
|
7
6
|
import { applyBrowserCors, authenticateSession, now } from './assistant-route-http.js';
|
|
8
7
|
import { sessionScopedTarget } from './assistant-session-target.js';
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { clientAddressBucket, } from '@noodle-borg/admission-limits/portable';
|
|
2
2
|
import { assistantModelFailure, assistantModelSource, assistantModelTransport, } from '@noodle-borg/assistant-gateway/model-runtime';
|
|
3
|
-
import { ASSISTANT_SESSION_IDLE_MS,
|
|
3
|
+
import { ASSISTANT_SESSION_IDLE_MS, effectiveAssistantBrowserConfiguration, executeAssistantAppToolCall, isAssistantPageContext, isAssistantVerifiedClaims, parseAssistantContextPreferences, parseAssistantCustomerRouting, refuseBridgeToolCall, refusePublicTurn, resolveInvocationContextSnapshot, resumeTurnMessage, resumeUnavailableMessage, shouldAutoResume, surfaceBindingForOrigin, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
4
4
|
import { canonicalizeAuthorizationClaimValues } from '@noodle-borg/auth';
|
|
5
|
-
import { validateJsonSchemaWithDefaults } from '@noodle-borg/compiler';
|
|
6
5
|
import { tenantMcpUrl } from '@noodle-borg/module';
|
|
7
|
-
import { assistantRefusedTurnUsageRequestEvent, assistantSessionUsageRequestEvent, assistantTurnNumber, assistantTurnUsageRequestEvent, captureAssistantUsage, } from '@noodle-borg/observability';
|
|
8
|
-
import {
|
|
6
|
+
import { assistantAppToolCallRequestEvent, assistantRefusedTurnUsageRequestEvent, assistantSessionUsageRequestEvent, assistantTurnNumber, assistantTurnUsageRequestEvent, captureAssistantUsage, } from '@noodle-borg/observability';
|
|
7
|
+
import { mapExecutionError, mapResourceContents, mapResourcesList, mapToolOutput, mapToolsList, } from '@noodle-borg/protocol';
|
|
9
8
|
import { executeResource } from '@noodle-borg/runtime';
|
|
10
9
|
import { assistantMessageTurnRequestSchema, assistantResumeTurnRequestSchema, assistantSessionResponseSchema, } from '@noodle-borg/wire-contracts';
|
|
11
10
|
import { readJsonBody, sendForbidden, sendJson, sendUnauthorized } from '../http-util.js';
|
|
12
|
-
import { resolveInvocationContextSnapshot } from '../invocation-context.js';
|
|
13
11
|
import { createAssistantTurnStats, runAgentTurn } from './assistant-agent.js';
|
|
14
12
|
import { elevateAssistantSession } from './assistant-elevation.js';
|
|
15
13
|
import { executeAssistantKnowledgeSearch, findAssistantKnowledgeComponent, resolveAssistantKnowledge, } from './assistant-knowledge.js';
|
|
@@ -99,7 +97,7 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
99
97
|
const declaredClaims = assistant.sessionClaims ?? {};
|
|
100
98
|
const claims = Object.fromEntries(Object.entries(isAssistantVerifiedClaims(parsed.claims) ? parsed.claims : {}).filter(([key]) => key in declaredClaims));
|
|
101
99
|
const current = now(deps);
|
|
102
|
-
const configuration = (await effectiveAssistantBrowserConfiguration(target.served.artifact.server, client.tenant, deps.appearance)).effective;
|
|
100
|
+
const configuration = (await effectiveAssistantBrowserConfiguration(target.served.artifact.server, client.tenant, deps.appearance, surfaceBinding.kind === 'pre-surfaces' ? undefined : surfaceBinding.kind)).effective;
|
|
103
101
|
const roles = canonicalizeAuthorizationClaimValues(parsed.user.roles, 'role');
|
|
104
102
|
const scopes = canonicalizeAuthorizationClaimValues(parsed.user.scopes, 'scope');
|
|
105
103
|
// Hoisted so an elevation and a fresh exchange bind the *same* principal. Two constructions would be
|
|
@@ -174,6 +172,16 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
174
172
|
captureAssistantUsage(deps.captureRequestEvent, assistantSessionUsageRequestEvent(session.session, performance.now() - usageStartedAt));
|
|
175
173
|
return sendJson(res, 201, sessionBody);
|
|
176
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Both turn entries — a resume and a typed message — refuse identically: the refusal is spent
|
|
177
|
+
* budget, so it is recorded against the surface before the caller is told no. Kept in one place so
|
|
178
|
+
* the two entries cannot drift into recording it differently.
|
|
179
|
+
*/
|
|
180
|
+
function refuseTurn(deps, res, session, refusal, usageStartedAt) {
|
|
181
|
+
const durationMs = performance.now() - usageStartedAt;
|
|
182
|
+
captureAssistantUsage(deps.captureRequestEvent, assistantRefusedTurnUsageRequestEvent(session, refusal.code, durationMs));
|
|
183
|
+
sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
|
|
184
|
+
}
|
|
177
185
|
export async function handleAssistantTurn(req, res, deps) {
|
|
178
186
|
const usageStartedAt = performance.now();
|
|
179
187
|
const session = await authenticateSession(req, res, deps);
|
|
@@ -201,10 +209,8 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
201
209
|
}
|
|
202
210
|
suggestionsRequested = resumeResult.data.suggestions === true;
|
|
203
211
|
const refusal = await refusePublicTurn(deps, session, 'resume', addressBucket);
|
|
204
|
-
if (refusal)
|
|
205
|
-
|
|
206
|
-
return sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
|
|
207
|
-
}
|
|
212
|
+
if (refusal)
|
|
213
|
+
return refuseTurn(deps, res, session, refusal, usageStartedAt);
|
|
208
214
|
const pending = await deps.store.consumePendingResume(session.id);
|
|
209
215
|
if (!pending) {
|
|
210
216
|
return sendJson(res, 409, {
|
|
@@ -252,10 +258,8 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
252
258
|
// Admission before any metered work (ADR 0201 §8): ahead of the deployment read and well ahead
|
|
253
259
|
// of the model, so a refused turn costs the customer nothing.
|
|
254
260
|
const refusal = await refusePublicTurn(deps, session, message, addressBucket);
|
|
255
|
-
if (refusal)
|
|
256
|
-
|
|
257
|
-
return sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
|
|
258
|
-
}
|
|
261
|
+
if (refusal)
|
|
262
|
+
return refuseTurn(deps, res, session, refusal, usageStartedAt);
|
|
259
263
|
// The visitor typed first: any pending post-sign-in resume is moot, and must not fire later.
|
|
260
264
|
if (session.pendingResume)
|
|
261
265
|
await deps.store.consumePendingResume(session.id);
|
|
@@ -399,15 +403,18 @@ export async function handleAssistantAppRequest(req, res, deps) {
|
|
|
399
403
|
const resource = target.served.artifact.resources?.find((candidate) => candidate.uri === params.uri);
|
|
400
404
|
if (!resource)
|
|
401
405
|
return sendJson(res, 404, { error: 'resource not found' });
|
|
406
|
+
// One projection for both the snapshot and the execution: they must agree, and building it
|
|
407
|
+
// twice was two chances for them not to.
|
|
408
|
+
const executeDeps = withAssistantSessionExecutionAuthority(target.served.deps, target.served.artifact, session);
|
|
402
409
|
const context = await resolveInvocationContextSnapshot({
|
|
403
410
|
artifact: target.served.artifact,
|
|
404
|
-
executeDeps
|
|
411
|
+
executeDeps,
|
|
405
412
|
caller: session.caller,
|
|
406
413
|
instant: now(deps),
|
|
407
414
|
...(session.preferences ? { applicationPreference: session.preferences } : {}),
|
|
408
415
|
});
|
|
409
416
|
const execution = await executeResource(target.served.artifact, resource.name, {}, {
|
|
410
|
-
...
|
|
417
|
+
...executeDeps,
|
|
411
418
|
caller: session.caller,
|
|
412
419
|
context,
|
|
413
420
|
});
|
|
@@ -418,56 +425,64 @@ export async function handleAssistantAppRequest(req, res, deps) {
|
|
|
418
425
|
if (method === 'tools/call') {
|
|
419
426
|
if (typeof params.name !== 'string')
|
|
420
427
|
return sendJson(res, 400, { error: 'tool name required' });
|
|
421
|
-
const
|
|
428
|
+
const startedAt = performance.now();
|
|
429
|
+
const toolName = params.name;
|
|
430
|
+
// Recorded on every exit below, including the refusals: a rejected browser-agent call is
|
|
431
|
+
// exactly the traffic an operator watching one needs to see (ADR 0220 decision 6).
|
|
432
|
+
const record = (outcome, errorKind) => captureAssistantUsage(deps.captureRequestEvent, assistantAppToolCallRequestEvent(session, {
|
|
433
|
+
toolName,
|
|
434
|
+
// Attribution only. The marker labels the row; the session decided authority above.
|
|
435
|
+
bridged,
|
|
436
|
+
outcome,
|
|
437
|
+
...(errorKind === undefined ? {} : { errorKind }),
|
|
438
|
+
durationMs: performance.now() - startedAt,
|
|
439
|
+
}));
|
|
440
|
+
const knowledgeComponent = findAssistantKnowledgeComponent(knowledge, toolName);
|
|
422
441
|
if (knowledge !== undefined && knowledgeComponent !== undefined) {
|
|
423
442
|
const content = await executeAssistantKnowledgeSearch(knowledge, knowledgeComponent, params.arguments ?? {});
|
|
443
|
+
record('ok');
|
|
424
444
|
return sendJson(res, 200, mapToolOutput(JSON.parse(content)));
|
|
425
445
|
}
|
|
426
|
-
const
|
|
427
|
-
|
|
446
|
+
const call = await executeAssistantAppToolCall({
|
|
447
|
+
artifact: target.served.artifact,
|
|
448
|
+
deps: target.served.deps,
|
|
449
|
+
session,
|
|
450
|
+
toolName,
|
|
451
|
+
arguments: params.arguments,
|
|
452
|
+
now: () => now(deps),
|
|
453
|
+
store: deps.store,
|
|
454
|
+
audit: deps.audit,
|
|
455
|
+
});
|
|
456
|
+
if (call.kind === 'not-found') {
|
|
457
|
+
record('refused', 'app_tool_not_found');
|
|
428
458
|
return sendJson(res, 404, { error: 'app tool not found' });
|
|
429
459
|
}
|
|
430
|
-
if (
|
|
460
|
+
if (call.kind === 'forbidden') {
|
|
461
|
+
record('refused', 'tool_forbidden');
|
|
431
462
|
return sendJson(res, 403, { error: 'tool forbidden' });
|
|
432
463
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
executeDeps: withAssistantSessionExecutionAuthority(target.served.deps, target.served.artifact, session),
|
|
436
|
-
caller: session.caller,
|
|
437
|
-
instant: now(deps),
|
|
438
|
-
...(session.preferences ? { applicationPreference: session.preferences } : {}),
|
|
439
|
-
});
|
|
440
|
-
const validated = validateJsonSchemaWithDefaults(tool.inputSchema, params.arguments ?? {});
|
|
441
|
-
if (validated.issues.length > 0) {
|
|
464
|
+
if (call.kind === 'invalid-arguments') {
|
|
465
|
+
record('refused', 'invalid_tool_arguments');
|
|
442
466
|
return sendJson(res, 200, {
|
|
443
467
|
content: [{ type: 'text', text: 'invalid tool arguments' }],
|
|
444
468
|
isError: true,
|
|
445
469
|
});
|
|
446
470
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
store: deps.store,
|
|
456
|
-
audit: deps.audit,
|
|
457
|
-
now: () => now(deps),
|
|
458
|
-
});
|
|
459
|
-
if (dispatched.kind === 'event') {
|
|
460
|
-
if (dispatched.event === 'tool_proposed' || dispatched.event === 'input_requested') {
|
|
461
|
-
return sendJson(res, 200, {
|
|
462
|
-
interaction: { event: dispatched.event, data: dispatched.data },
|
|
463
|
-
});
|
|
464
|
-
}
|
|
471
|
+
if (call.kind === 'interaction') {
|
|
472
|
+
// A raised confirmation is not a failure and not yet an execution: the bridge did what it
|
|
473
|
+
// should, and the interaction is answered on its own route.
|
|
474
|
+
record('ok');
|
|
475
|
+
return sendJson(res, 200, { interaction: { event: call.event, data: call.data } });
|
|
476
|
+
}
|
|
477
|
+
if (call.kind === 'error') {
|
|
478
|
+
record('failed', call.code);
|
|
465
479
|
return sendJson(res, 200, {
|
|
466
|
-
content: [{ type: 'text', text:
|
|
480
|
+
content: [{ type: 'text', text: call.code }],
|
|
467
481
|
isError: true,
|
|
468
482
|
});
|
|
469
483
|
}
|
|
470
|
-
|
|
484
|
+
record('ok');
|
|
485
|
+
return sendJson(res, 200, mapToolOutput(call.output));
|
|
471
486
|
}
|
|
472
487
|
return sendJson(res, 400, { error: 'unsupported MCP App method' });
|
|
473
488
|
}
|
|
@@ -14,9 +14,6 @@ export async function handleIntentCaptureSettings(req, res, ref, deps) {
|
|
|
14
14
|
const identity = await authorizeTenantControl(req, res, deps.gate, deps.controlPlane, ref.org);
|
|
15
15
|
if (identity === false)
|
|
16
16
|
return;
|
|
17
|
-
if (!deps.previewOrgs.has(ref.org)) {
|
|
18
|
-
return sendJson(res, 403, { ok: false, error: 'intent capture preview is unavailable' });
|
|
19
|
-
}
|
|
20
17
|
const setting = await deps.settings.get(ref);
|
|
21
18
|
return sendJson(res, 200, {
|
|
22
19
|
ok: true,
|
|
@@ -32,9 +29,6 @@ export async function handleIntentCaptureSettings(req, res, ref, deps) {
|
|
|
32
29
|
const identity = await authorizeOwner(req, res, ref, deps);
|
|
33
30
|
if (identity === undefined)
|
|
34
31
|
return;
|
|
35
|
-
if (!deps.previewOrgs.has(ref.org)) {
|
|
36
|
-
return sendJson(res, 403, { ok: false, error: 'intent capture preview is unavailable' });
|
|
37
|
-
}
|
|
38
32
|
const body = await readJsonBody(req, deps.maxBody);
|
|
39
33
|
if (!body.ok)
|
|
40
34
|
return sendJson(res, body.status, { error: body.error });
|
|
@@ -74,9 +68,6 @@ export async function handleIntentCaptureSettings(req, res, ref, deps) {
|
|
|
74
68
|
const identity = await authorizeOwner(req, res, ref, deps);
|
|
75
69
|
if (identity === undefined)
|
|
76
70
|
return;
|
|
77
|
-
if (!deps.previewOrgs.has(ref.org)) {
|
|
78
|
-
return sendJson(res, 403, { ok: false, error: 'intent capture preview is unavailable' });
|
|
79
|
-
}
|
|
80
71
|
await deps.settings.delete(ref);
|
|
81
72
|
const purged = await deps.intents.purge(ref);
|
|
82
73
|
await deps.audit.emit({
|
|
@@ -98,9 +89,6 @@ export async function handleIntentInsights(req, res, ref, url, deps) {
|
|
|
98
89
|
const identity = await authorizeTenantControl(req, res, deps.gate, deps.controlPlane, ref.org);
|
|
99
90
|
if (identity === false)
|
|
100
91
|
return;
|
|
101
|
-
if (!deps.previewOrgs.has(ref.org)) {
|
|
102
|
-
return sendJson(res, 403, { ok: false, error: 'intent capture preview is unavailable' });
|
|
103
|
-
}
|
|
104
92
|
const windowName = url.searchParams.get('window') ?? '7d';
|
|
105
93
|
const windowMs = WINDOWS_MS.get(windowName);
|
|
106
94
|
if (windowMs === undefined) {
|
|
@@ -57,7 +57,7 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
57
57
|
let activeAudit;
|
|
58
58
|
const configStore = options.configStore ?? registry.configStore;
|
|
59
59
|
const alertRuleStore = options.alertRuleStore ?? new InMemoryAlertRuleStore();
|
|
60
|
-
const [intentSettings, intentEvents, requestEvents
|
|
60
|
+
const [intentSettings, intentEvents, requestEvents] = createObservabilityStores(options);
|
|
61
61
|
const assistantStore = options.assistantStore ?? new InMemoryAssistantStore();
|
|
62
62
|
const assistantAppearance = options.assistantAppearance ?? new InMemoryAssistantAppearanceSettingsStore();
|
|
63
63
|
const anonymousLimiter = new AnonymousConsumerLimiter();
|
|
@@ -76,7 +76,7 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
76
76
|
activeAudit = audit;
|
|
77
77
|
const { publicTenantRouting } = mcp.mcpRoutingOptions(options, controlPlane);
|
|
78
78
|
const resolveEndpointOptions = (org) => mcp.endpointUrlOptionsForOrg(options, controlPlane, org);
|
|
79
|
-
const withIntentMode = createIntentTargetResolver(intentSettings
|
|
79
|
+
const withIntentMode = createIntentTargetResolver(intentSettings);
|
|
80
80
|
const router = createMcpRouter(async (id) => withIntentMode(await registry.getServing(id)), {
|
|
81
81
|
logger,
|
|
82
82
|
tls,
|
|
@@ -122,7 +122,6 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
122
122
|
requestEvents,
|
|
123
123
|
intentEvents,
|
|
124
124
|
intentSettings,
|
|
125
|
-
previewOrgs: intentPreviewOrgs,
|
|
126
125
|
audit,
|
|
127
126
|
maxBody,
|
|
128
127
|
tls,
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
41
41
|
"@noodle-borg/admission-limits": "0.0.0",
|
|
42
|
-
"@noodle-borg/agent-kit": "0.
|
|
42
|
+
"@noodle-borg/agent-kit": "0.89.0",
|
|
43
43
|
"@noodle-borg/app-package": "0.0.0",
|
|
44
44
|
"@noodle-borg/assistant-gateway": "0.0.0",
|
|
45
45
|
"@noodle-borg/auth": "0.0.0",
|