@stackbone/cli 0.9.3 → 0.9.4
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/CHANGELOG.md +31 -0
- package/main.js +915 -468
- package/package.json +1 -1
- package/stackbone-cli-0.9.4.tgz +0 -0
- package/voice-worker/main.js +9 -2
- package/voice-worker/main.js.map +3 -3
- package/stackbone-cli-0.9.3.tgz +0 -0
package/main.js
CHANGED
|
@@ -7,7 +7,7 @@ import "semver/functions/compare.js";
|
|
|
7
7
|
import "semver/functions/prerelease.js";
|
|
8
8
|
import "semver/functions/valid.js";
|
|
9
9
|
import { access, appendFile, chmod, constants, copyFile, cp, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
10
|
-
import { homedir, tmpdir } from "node:os";
|
|
10
|
+
import { homedir, release, tmpdir } from "node:os";
|
|
11
11
|
import path, { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
12
12
|
import semver from "semver";
|
|
13
13
|
import pino from "pino";
|
|
@@ -132,7 +132,7 @@ var init_organizations = __esmMin((() => {
|
|
|
132
132
|
name: z.string().trim().min(1).max(120).optional(),
|
|
133
133
|
tier: z.literal("free").default("free")
|
|
134
134
|
});
|
|
135
|
-
})), modelProviderSlotSchema, MODEL_PROVIDER_SLOTS, SLOT_NAMES, modelProviderSourceSchema, isModelProviderValuePresent, firstPresentName, resolveModelProviderSlots, overriddenExportedSlots, MODEL_PROVIDER_OVERRIDE_SECRET, storedOverridesSchema, encodeModelProviderOverrides, decodeModelProviderOverrides, writeModelProviderSlotsToEnv, modelProviderKindSchema, modelProviderGetResponseSchema, modelProviderPutRequestSchema, modelProviderAgentsOutcomeSchema, modelProviderPutResponseSchema, modelProviderFirstRunResponseSchema, modelProviderKeyCheckSchema, modelProviderTestResponseSchema, modelProviderModelTypeSchema, modelProviderModelSchema, canServeText, modelProviderModelsRequestSchema, modelProviderModelsResponseSchema, MODEL_PROVIDER_DEFAULT_ID, MODEL_PROVIDER_ID_PATTERN, modelProviderIdSchema, pickedModelProviderIdSchema, pinModelId, unpinModelId, modelProviderListItemSchema, modelProviderListResponseSchema, providerFields, modelProviderCreateRequestSchema, modelProviderUpdateRequestSchema;
|
|
135
|
+
})), modelProviderSlotSchema, MODEL_PROVIDER_SLOTS, SLOT_NAMES, modelProviderSourceSchema, isModelProviderValuePresent, firstPresentName, resolveModelProviderSlots, overriddenExportedSlots, MODEL_PROVIDER_OVERRIDE_SECRET, storedOverridesSchema, encodeModelProviderOverrides, decodeModelProviderOverrides, writeModelProviderSlotsToEnv, modelProviderKindSchema, modelProviderGetResponseSchema, modelProviderPutRequestSchema, modelProviderAgentsOutcomeSchema, modelProviderPutResponseSchema, modelProviderFirstRunResponseSchema, modelProviderKeyCheckSchema, modelProviderTestResponseSchema, modelProviderModelTypeSchema, modelProviderModelSchema, canServeText, modelProviderModelsRequestSchema, modelProviderModelsResponseSchema, MODEL_PROVIDER_DEFAULT_ID, LIVEKIT_MODEL_PROVIDER_ID, MODEL_PROVIDER_ID_PATTERN, modelProviderIdSchema, pickedModelProviderIdSchema, pinModelId, unpinModelId, modelProviderListItemSchema, modelProviderListResponseSchema, providerFields, modelProviderCreateRequestSchema, modelProviderUpdateRequestSchema;
|
|
136
136
|
var init_studio_model_provider = __esmMin((() => {
|
|
137
137
|
modelProviderSlotSchema = z.enum(["baseUrl", "apiKey"]);
|
|
138
138
|
MODEL_PROVIDER_SLOTS = {
|
|
@@ -226,7 +226,8 @@ var init_studio_model_provider = __esmMin((() => {
|
|
|
226
226
|
"openrouter",
|
|
227
227
|
"ollama",
|
|
228
228
|
"lmstudio",
|
|
229
|
-
"generic"
|
|
229
|
+
"generic",
|
|
230
|
+
"livekit"
|
|
230
231
|
]);
|
|
231
232
|
modelProviderGetResponseSchema = z.object({
|
|
232
233
|
configured: z.boolean(),
|
|
@@ -301,6 +302,7 @@ var init_studio_model_provider = __esmMin((() => {
|
|
|
301
302
|
warning: z.string().optional()
|
|
302
303
|
});
|
|
303
304
|
MODEL_PROVIDER_DEFAULT_ID = "default";
|
|
305
|
+
LIVEKIT_MODEL_PROVIDER_ID = "livekit";
|
|
304
306
|
MODEL_PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]{0,30}[a-z0-9]$/;
|
|
305
307
|
modelProviderIdSchema = z.string().regex(MODEL_PROVIDER_ID_PATTERN, { message: "Lowercase letters, digits and dashes, 2 to 32, starting with a letter." }).refine((id) => id !== MODEL_PROVIDER_DEFAULT_ID, { message: `'${MODEL_PROVIDER_DEFAULT_ID}' is provider 1's id.` });
|
|
306
308
|
pickedModelProviderIdSchema = z.string().regex(MODEL_PROVIDER_ID_PATTERN);
|
|
@@ -323,7 +325,11 @@ var init_studio_model_provider = __esmMin((() => {
|
|
|
323
325
|
kind: modelProviderKindSchema,
|
|
324
326
|
baseUrl: z.string(),
|
|
325
327
|
apiKey: z.string().nullable(),
|
|
326
|
-
source: z.enum([
|
|
328
|
+
source: z.enum([
|
|
329
|
+
"env",
|
|
330
|
+
"local",
|
|
331
|
+
"voice"
|
|
332
|
+
]),
|
|
327
333
|
removable: z.boolean(),
|
|
328
334
|
responding: z.boolean().nullable(),
|
|
329
335
|
error: z.string().optional()
|
|
@@ -997,9 +1003,9 @@ function requireCapability$1(capability, contract) {
|
|
|
997
1003
|
error: null
|
|
998
1004
|
};
|
|
999
1005
|
}
|
|
1000
|
-
var CAPABILITIES$
|
|
1006
|
+
var CAPABILITIES$2, MODULE_CAPABILITIES, contractBuildNameSchema, contractResponseSchema;
|
|
1001
1007
|
var init_contract$1 = __esmMin((() => {
|
|
1002
|
-
CAPABILITIES$
|
|
1008
|
+
CAPABILITIES$2 = [
|
|
1003
1009
|
"database.postgres_direct",
|
|
1004
1010
|
"database.drizzle",
|
|
1005
1011
|
"rag.basic",
|
|
@@ -1017,7 +1023,7 @@ var init_contract$1 = __esmMin((() => {
|
|
|
1017
1023
|
"insights.basic",
|
|
1018
1024
|
"limits.basic"
|
|
1019
1025
|
];
|
|
1020
|
-
new Set(CAPABILITIES$
|
|
1026
|
+
new Set(CAPABILITIES$2);
|
|
1021
1027
|
MODULE_CAPABILITIES = {
|
|
1022
1028
|
database: "database.postgres_direct",
|
|
1023
1029
|
rag: "rag.basic",
|
|
@@ -3045,7 +3051,7 @@ var init_studio_internal_prompts = __esmMin((() => {
|
|
|
3045
3051
|
ownerName: "guardrail-classifier",
|
|
3046
3052
|
key: GUIDANCE_KEY,
|
|
3047
3053
|
title: "Guardrail classifier",
|
|
3048
|
-
description: "Frames the model a model-backed guardrail asks about a turn (prompt injection, moderation, off-limits subjects). The question, the answer format and the rule that the checked text is data are added after it.",
|
|
3054
|
+
description: "Frames the model a model-backed guardrail asks about a turn (prompt injection, moderation, off-limits subjects). The question, the answer format and the rule that the checked text is data are added after it. On an OpenRouter box the decision model (Jev) decides the incoming message and the reply first and does not read this text; it frames the checking model only, when Jev cannot answer. Put any nuance in the rule's own text.",
|
|
3049
3055
|
model: {
|
|
3050
3056
|
target: "setting",
|
|
3051
3057
|
settingKey: "guardrailModel"
|
|
@@ -5693,11 +5699,14 @@ var init_studio_triggers = __esmMin((() => {
|
|
|
5693
5699
|
/** Where the sample event came from (STA3-45 D5): the named link's own first
|
|
5694
5700
|
* event (`link`), the provider's newest event fetched for a link that had none
|
|
5695
5701
|
* (`fetched`), or the trigger's declared sample (`declared`) — which is also the
|
|
5696
|
-
* answer when that fetch found nothing or failed.
|
|
5702
|
+
* answer when that fetch found nothing or failed. `none` is a webhook trigger
|
|
5703
|
+
* with no event of its own yet (STA3-121): nothing was evaluated, `output` is
|
|
5704
|
+
* `null` and `issues` empty, so it is no verdict, not a passing one. */
|
|
5697
5705
|
sampleSource: z.enum([
|
|
5698
5706
|
"link",
|
|
5699
5707
|
"fetched",
|
|
5700
|
-
"declared"
|
|
5708
|
+
"declared",
|
|
5709
|
+
"none"
|
|
5701
5710
|
]),
|
|
5702
5711
|
/** The event fetched from the provider, present only on a `fetched` answer: the
|
|
5703
5712
|
* one evaluated over, even when the link already kept another in the meantime.
|
|
@@ -11650,7 +11659,7 @@ var init_tool_call_ids = __esmMin((() => {
|
|
|
11650
11659
|
//#endregion
|
|
11651
11660
|
//#region ../../libs/shared/model-provider-wire/src/lib/common/rules.ts
|
|
11652
11661
|
var withNormalizedToolCalls, withNormalizedToolCallId, normalizeMessage, applyCommonRules;
|
|
11653
|
-
var init_rules$
|
|
11662
|
+
var init_rules$5 = __esmMin((() => {
|
|
11654
11663
|
init_json();
|
|
11655
11664
|
init_reasoning();
|
|
11656
11665
|
init_tool_call_ids();
|
|
@@ -11694,9 +11703,15 @@ var init_rules$4 = __esmMin((() => {
|
|
|
11694
11703
|
};
|
|
11695
11704
|
}));
|
|
11696
11705
|
//#endregion
|
|
11706
|
+
//#region ../../libs/shared/model-provider-wire/src/lib/common/gateway.ts
|
|
11707
|
+
var keepRequest;
|
|
11708
|
+
var init_gateway = __esmMin((() => {
|
|
11709
|
+
keepRequest = (body) => body;
|
|
11710
|
+
}));
|
|
11711
|
+
//#endregion
|
|
11697
11712
|
//#region ../../libs/shared/model-provider-wire/src/lib/common/catalogue.ts
|
|
11698
11713
|
var UNKNOWN_CAPABILITIES, openAiRoot, nativeRoot, dataEntriesOf, responsesFor, listedEntries, fromCapabilityList;
|
|
11699
|
-
var init_catalogue$
|
|
11714
|
+
var init_catalogue$6 = __esmMin((() => {
|
|
11700
11715
|
init_json();
|
|
11701
11716
|
UNKNOWN_CAPABILITIES = {
|
|
11702
11717
|
tools: null,
|
|
@@ -11815,8 +11830,8 @@ var init_model_type = __esmMin((() => {
|
|
|
11815
11830
|
//#endregion
|
|
11816
11831
|
//#region ../../libs/shared/model-provider-wire/src/lib/generic/catalogue.ts
|
|
11817
11832
|
var LITELLM_MODES, typeOf$2, genericCatalogue;
|
|
11818
|
-
var init_catalogue$
|
|
11819
|
-
init_catalogue$
|
|
11833
|
+
var init_catalogue$5 = __esmMin((() => {
|
|
11834
|
+
init_catalogue$6();
|
|
11820
11835
|
init_json();
|
|
11821
11836
|
init_model_type();
|
|
11822
11837
|
LITELLM_MODES = new Map([
|
|
@@ -11851,25 +11866,97 @@ var init_catalogue$4 = __esmMin((() => {
|
|
|
11851
11866
|
return types;
|
|
11852
11867
|
},
|
|
11853
11868
|
fallbackType: genericFallbackType,
|
|
11854
|
-
refusesUnknownModelId: false
|
|
11869
|
+
refusesUnknownModelId: false,
|
|
11870
|
+
fixedModelIds: null
|
|
11855
11871
|
};
|
|
11856
11872
|
}));
|
|
11857
11873
|
//#endregion
|
|
11858
11874
|
//#region ../../libs/shared/model-provider-wire/src/lib/generic/rules.ts
|
|
11859
11875
|
var genericRules;
|
|
11860
|
-
var init_rules$
|
|
11861
|
-
|
|
11876
|
+
var init_rules$4 = __esmMin((() => {
|
|
11877
|
+
init_gateway();
|
|
11878
|
+
init_catalogue$5();
|
|
11862
11879
|
genericRules = {
|
|
11863
11880
|
wantsReasoning: () => false,
|
|
11864
11881
|
keepsProviderRouting: false,
|
|
11882
|
+
adaptRequest: keepRequest,
|
|
11865
11883
|
catalogue: genericCatalogue
|
|
11866
11884
|
};
|
|
11867
11885
|
}));
|
|
11868
11886
|
//#endregion
|
|
11887
|
+
//#region ../../libs/shared/model-provider-wire/src/lib/livekit/catalogue.ts
|
|
11888
|
+
var LIVEKIT_INFERENCE_MODEL_IDS, LISTED, CAPABILITIES$1, TYPES, livekitCatalogue;
|
|
11889
|
+
var init_catalogue$4 = __esmMin((() => {
|
|
11890
|
+
LIVEKIT_INFERENCE_MODEL_IDS = [
|
|
11891
|
+
"google/gemma-4-31b-it",
|
|
11892
|
+
"google/gemini-3.1-flash-lite",
|
|
11893
|
+
"google/gemini-3.5-flash",
|
|
11894
|
+
"google/gemini-3-flash",
|
|
11895
|
+
"google/gemini-3.1-pro",
|
|
11896
|
+
"google/gemini-2.5-flash-lite",
|
|
11897
|
+
"google/gemini-2.5-flash",
|
|
11898
|
+
"google/gemini-2.5-pro",
|
|
11899
|
+
"openai/gpt-5.5",
|
|
11900
|
+
"openai/gpt-5.4",
|
|
11901
|
+
"openai/gpt-5.4-mini",
|
|
11902
|
+
"openai/gpt-5.4-nano",
|
|
11903
|
+
"openai/gpt-5.2",
|
|
11904
|
+
"openai/gpt-5.1",
|
|
11905
|
+
"openai/gpt-5",
|
|
11906
|
+
"openai/gpt-5-mini",
|
|
11907
|
+
"openai/gpt-5-nano",
|
|
11908
|
+
"openai/gpt-4.1",
|
|
11909
|
+
"openai/gpt-4.1-mini",
|
|
11910
|
+
"openai/gpt-4.1-nano",
|
|
11911
|
+
"openai/gpt-4o",
|
|
11912
|
+
"openai/gpt-4o-mini",
|
|
11913
|
+
"openai/gpt-oss-120b",
|
|
11914
|
+
"moonshotai/kimi-k2.6",
|
|
11915
|
+
"deepseek-ai/deepseek-v3.2",
|
|
11916
|
+
"zai/glm-5.1",
|
|
11917
|
+
"xai/grok-4.3"
|
|
11918
|
+
];
|
|
11919
|
+
LISTED = {
|
|
11920
|
+
tools: true,
|
|
11921
|
+
reasoning: null
|
|
11922
|
+
};
|
|
11923
|
+
CAPABILITIES$1 = new Map(LIVEKIT_INFERENCE_MODEL_IDS.map((id) => [id, LISTED]));
|
|
11924
|
+
TYPES = new Map(LIVEKIT_INFERENCE_MODEL_IDS.map((id) => [id, "chat"]));
|
|
11925
|
+
livekitCatalogue = {
|
|
11926
|
+
catalogueSources: () => [],
|
|
11927
|
+
capabilities: () => CAPABILITIES$1,
|
|
11928
|
+
types: () => TYPES,
|
|
11929
|
+
fallbackType: () => "chat",
|
|
11930
|
+
refusesUnknownModelId: false,
|
|
11931
|
+
fixedModelIds: LIVEKIT_INFERENCE_MODEL_IDS
|
|
11932
|
+
};
|
|
11933
|
+
}));
|
|
11934
|
+
//#endregion
|
|
11935
|
+
//#region ../../libs/shared/model-provider-wire/src/lib/livekit/rules.ts
|
|
11936
|
+
var COMPLETION_TOKENS_PREFIX, withCompletionTokens, livekitRules;
|
|
11937
|
+
var init_rules$3 = __esmMin((() => {
|
|
11938
|
+
init_catalogue$4();
|
|
11939
|
+
COMPLETION_TOKENS_PREFIX = "openai/gpt-5";
|
|
11940
|
+
withCompletionTokens = (body, modelId) => {
|
|
11941
|
+
if (!modelId?.startsWith(COMPLETION_TOKENS_PREFIX) || !("max_tokens" in body)) return body;
|
|
11942
|
+
const { max_tokens: maxTokens, ...rest } = body;
|
|
11943
|
+
return "max_completion_tokens" in rest ? rest : {
|
|
11944
|
+
...rest,
|
|
11945
|
+
max_completion_tokens: maxTokens
|
|
11946
|
+
};
|
|
11947
|
+
};
|
|
11948
|
+
livekitRules = {
|
|
11949
|
+
wantsReasoning: () => false,
|
|
11950
|
+
keepsProviderRouting: false,
|
|
11951
|
+
adaptRequest: withCompletionTokens,
|
|
11952
|
+
catalogue: livekitCatalogue
|
|
11953
|
+
};
|
|
11954
|
+
}));
|
|
11955
|
+
//#endregion
|
|
11869
11956
|
//#region ../../libs/shared/model-provider-wire/src/lib/lmstudio/catalogue.ts
|
|
11870
11957
|
var capabilitiesOf$2, typeOf$1, describedModels, lmstudioCatalogue;
|
|
11871
11958
|
var init_catalogue$3 = __esmMin((() => {
|
|
11872
|
-
init_catalogue$
|
|
11959
|
+
init_catalogue$6();
|
|
11873
11960
|
init_model_type$1();
|
|
11874
11961
|
init_json();
|
|
11875
11962
|
capabilitiesOf$2 = (entry) => {
|
|
@@ -11913,17 +12000,20 @@ var init_catalogue$3 = __esmMin((() => {
|
|
|
11913
12000
|
return types;
|
|
11914
12001
|
},
|
|
11915
12002
|
fallbackType: deriveModelType,
|
|
11916
|
-
refusesUnknownModelId: true
|
|
12003
|
+
refusesUnknownModelId: true,
|
|
12004
|
+
fixedModelIds: null
|
|
11917
12005
|
};
|
|
11918
12006
|
}));
|
|
11919
12007
|
//#endregion
|
|
11920
12008
|
//#region ../../libs/shared/model-provider-wire/src/lib/lmstudio/rules.ts
|
|
11921
12009
|
var lmstudioRules;
|
|
11922
12010
|
var init_rules$2 = __esmMin((() => {
|
|
12011
|
+
init_gateway();
|
|
11923
12012
|
init_catalogue$3();
|
|
11924
12013
|
lmstudioRules = {
|
|
11925
12014
|
wantsReasoning: () => false,
|
|
11926
12015
|
keepsProviderRouting: false,
|
|
12016
|
+
adaptRequest: keepRequest,
|
|
11927
12017
|
catalogue: lmstudioCatalogue
|
|
11928
12018
|
};
|
|
11929
12019
|
}));
|
|
@@ -11931,7 +12021,7 @@ var init_rules$2 = __esmMin((() => {
|
|
|
11931
12021
|
//#region ../../libs/shared/model-provider-wire/src/lib/ollama/catalogue.ts
|
|
11932
12022
|
var TOOLS_CAPABILITIES, REASONING_CAPABILITIES, CHAT_CAPABILITIES, EMBEDDING_CAPABILITIES, capabilitiesOf$1, typeOf, taggedModels, taggedId, describedCapabilities, ollamaCatalogue;
|
|
11933
12023
|
var init_catalogue$2 = __esmMin((() => {
|
|
11934
|
-
init_catalogue$
|
|
12024
|
+
init_catalogue$6();
|
|
11935
12025
|
init_model_type$1();
|
|
11936
12026
|
init_json();
|
|
11937
12027
|
TOOLS_CAPABILITIES = ["tools"];
|
|
@@ -12004,17 +12094,20 @@ var init_catalogue$2 = __esmMin((() => {
|
|
|
12004
12094
|
return types;
|
|
12005
12095
|
},
|
|
12006
12096
|
fallbackType: deriveModelType,
|
|
12007
|
-
refusesUnknownModelId: false
|
|
12097
|
+
refusesUnknownModelId: false,
|
|
12098
|
+
fixedModelIds: null
|
|
12008
12099
|
};
|
|
12009
12100
|
}));
|
|
12010
12101
|
//#endregion
|
|
12011
12102
|
//#region ../../libs/shared/model-provider-wire/src/lib/ollama/rules.ts
|
|
12012
12103
|
var ollamaRules;
|
|
12013
12104
|
var init_rules$1 = __esmMin((() => {
|
|
12105
|
+
init_gateway();
|
|
12014
12106
|
init_catalogue$2();
|
|
12015
12107
|
ollamaRules = {
|
|
12016
12108
|
wantsReasoning: () => false,
|
|
12017
12109
|
keepsProviderRouting: false,
|
|
12110
|
+
adaptRequest: keepRequest,
|
|
12018
12111
|
catalogue: ollamaCatalogue
|
|
12019
12112
|
};
|
|
12020
12113
|
}));
|
|
@@ -12022,7 +12115,7 @@ var init_rules$1 = __esmMin((() => {
|
|
|
12022
12115
|
//#region ../../libs/shared/model-provider-wire/src/lib/openrouter/catalogue.ts
|
|
12023
12116
|
var TOOLS_PARAMETERS, REASONING_PARAMETERS, capabilitiesOf, openrouterCatalogue;
|
|
12024
12117
|
var init_catalogue$1 = __esmMin((() => {
|
|
12025
|
-
init_catalogue$
|
|
12118
|
+
init_catalogue$6();
|
|
12026
12119
|
init_model_type$1();
|
|
12027
12120
|
TOOLS_PARAMETERS = ["tools"];
|
|
12028
12121
|
REASONING_PARAMETERS = ["reasoning", "include_reasoning"];
|
|
@@ -12042,18 +12135,21 @@ var init_catalogue$1 = __esmMin((() => {
|
|
|
12042
12135
|
},
|
|
12043
12136
|
types: () => /* @__PURE__ */ new Map(),
|
|
12044
12137
|
fallbackType: deriveModelType,
|
|
12045
|
-
refusesUnknownModelId: false
|
|
12138
|
+
refusesUnknownModelId: false,
|
|
12139
|
+
fixedModelIds: null
|
|
12046
12140
|
};
|
|
12047
12141
|
}));
|
|
12048
12142
|
//#endregion
|
|
12049
12143
|
//#region ../../libs/shared/model-provider-wire/src/lib/openrouter/rules.ts
|
|
12050
12144
|
var openrouterRules;
|
|
12051
12145
|
var init_rules = __esmMin((() => {
|
|
12146
|
+
init_gateway();
|
|
12052
12147
|
init_reasoning_request();
|
|
12053
12148
|
init_catalogue$1();
|
|
12054
12149
|
openrouterRules = {
|
|
12055
12150
|
wantsReasoning: (modelId) => !replaysSignedReasoning(modelId),
|
|
12056
12151
|
keepsProviderRouting: true,
|
|
12152
|
+
adaptRequest: keepRequest,
|
|
12057
12153
|
catalogue: openrouterCatalogue
|
|
12058
12154
|
};
|
|
12059
12155
|
}));
|
|
@@ -12061,6 +12157,7 @@ var init_rules = __esmMin((() => {
|
|
|
12061
12157
|
//#region ../../libs/shared/model-provider-wire/src/lib/gateway-rules.ts
|
|
12062
12158
|
var BY_KIND, gatewayRulesFor;
|
|
12063
12159
|
var init_gateway_rules = __esmMin((() => {
|
|
12160
|
+
init_rules$4();
|
|
12064
12161
|
init_rules$3();
|
|
12065
12162
|
init_rules$2();
|
|
12066
12163
|
init_rules$1();
|
|
@@ -12069,7 +12166,8 @@ var init_gateway_rules = __esmMin((() => {
|
|
|
12069
12166
|
openrouter: openrouterRules,
|
|
12070
12167
|
ollama: ollamaRules,
|
|
12071
12168
|
lmstudio: lmstudioRules,
|
|
12072
|
-
generic: genericRules
|
|
12169
|
+
generic: genericRules,
|
|
12170
|
+
livekit: livekitRules
|
|
12073
12171
|
};
|
|
12074
12172
|
gatewayRulesFor = (kind) => BY_KIND[kind];
|
|
12075
12173
|
}));
|
|
@@ -12080,20 +12178,27 @@ var init_normalize_request = __esmMin((() => {
|
|
|
12080
12178
|
init_json();
|
|
12081
12179
|
init_provider_routing();
|
|
12082
12180
|
init_reasoning_request();
|
|
12083
|
-
init_rules$
|
|
12181
|
+
init_rules$5();
|
|
12084
12182
|
init_gateway_rules();
|
|
12085
12183
|
normalizeRequest = (body, target) => {
|
|
12086
12184
|
if (!isJsonRecord(body)) return body;
|
|
12087
12185
|
const common = applyCommonRules(body);
|
|
12088
12186
|
const rules = gatewayRulesFor(target.gatewayKind);
|
|
12089
|
-
|
|
12187
|
+
const routed = withProviderRouting(withReasoningRequest(common, rules.wantsReasoning(target.modelId)), rules.keepsProviderRouting);
|
|
12188
|
+
return rules.adaptRequest(routed, target.modelId);
|
|
12090
12189
|
};
|
|
12091
|
-
})), OPENROUTER_HOST, gatewayKindForBaseUrl;
|
|
12190
|
+
})), OPENROUTER_HOST, LIVEKIT_INFERENCE_BASE_URL, LIVEKIT_INFERENCE_HOST, KIND_BY_HOST, gatewayKindForBaseUrl;
|
|
12092
12191
|
var init_target = __esmMin((() => {
|
|
12093
12192
|
OPENROUTER_HOST = "openrouter.ai";
|
|
12193
|
+
LIVEKIT_INFERENCE_BASE_URL = "https://agent-gateway.livekit.cloud/v1";
|
|
12194
|
+
LIVEKIT_INFERENCE_HOST = "agent-gateway.livekit.cloud";
|
|
12195
|
+
KIND_BY_HOST = {
|
|
12196
|
+
[OPENROUTER_HOST]: "openrouter",
|
|
12197
|
+
[LIVEKIT_INFERENCE_HOST]: "livekit"
|
|
12198
|
+
};
|
|
12094
12199
|
gatewayKindForBaseUrl = (baseUrl) => {
|
|
12095
12200
|
try {
|
|
12096
|
-
return new URL(baseUrl).hostname.replace(/\.$/, "")
|
|
12201
|
+
return KIND_BY_HOST[new URL(baseUrl).hostname.replace(/\.$/, "")] ?? "generic";
|
|
12097
12202
|
} catch {
|
|
12098
12203
|
return "generic";
|
|
12099
12204
|
}
|
|
@@ -12183,9 +12288,9 @@ var init_catalogue_entry = __esmMin((() => {
|
|
|
12183
12288
|
}));
|
|
12184
12289
|
//#endregion
|
|
12185
12290
|
//#region ../../libs/shared/model-provider-wire/src/lib/catalogue.ts
|
|
12186
|
-
var modelsListSource, commonSources, catalogueSources, parseCatalogue, listedModelIds, checkModelId;
|
|
12291
|
+
var modelsListSource, commonSources, catalogueSources, parseCatalogue, fixedCatalogue, listedModelIds, checkModelId;
|
|
12187
12292
|
var init_catalogue = __esmMin((() => {
|
|
12188
|
-
init_catalogue$
|
|
12293
|
+
init_catalogue$6();
|
|
12189
12294
|
init_catalogue_entry();
|
|
12190
12295
|
init_gateway_rules();
|
|
12191
12296
|
modelsListSource = (baseUrl) => ({
|
|
@@ -12226,6 +12331,14 @@ var init_catalogue = __esmMin((() => {
|
|
|
12226
12331
|
}
|
|
12227
12332
|
return models;
|
|
12228
12333
|
};
|
|
12334
|
+
fixedCatalogue = (kind, baseUrl) => {
|
|
12335
|
+
const ids = gatewayRulesFor(kind).catalogue.fixedModelIds;
|
|
12336
|
+
if (ids === null) return null;
|
|
12337
|
+
return parseCatalogue(kind, [{
|
|
12338
|
+
source: modelsListSource(baseUrl),
|
|
12339
|
+
json: { data: ids.map((id) => ({ id })) }
|
|
12340
|
+
}]);
|
|
12341
|
+
};
|
|
12229
12342
|
listedModelIds = (json) => dataEntriesOf(json).flatMap((entry) => {
|
|
12230
12343
|
const id = entry["id"];
|
|
12231
12344
|
return typeof id === "string" ? [id] : [];
|
|
@@ -12255,10 +12368,14 @@ var init_src$18 = __esmMin((() => {
|
|
|
12255
12368
|
}));
|
|
12256
12369
|
//#endregion
|
|
12257
12370
|
//#region ../../libs/sdk/src/surfaces/external/ai/model-call-target.ts
|
|
12258
|
-
var MODEL_PROXY_URL_ENV, MODEL_PROXY_TOKEN_ENV, present$2, resolveModelCallTarget, pinModelForCallTarget;
|
|
12371
|
+
var STACKBONE_DEFAULT_HEADERS, MODEL_PROXY_URL_ENV, MODEL_PROXY_TOKEN_ENV, present$2, resolveModelCallTarget, pinModelForCallTarget;
|
|
12259
12372
|
var init_model_call_target = __esmMin((() => {
|
|
12260
12373
|
init_src$26();
|
|
12261
12374
|
init_src$18();
|
|
12375
|
+
STACKBONE_DEFAULT_HEADERS = {
|
|
12376
|
+
"HTTP-Referer": "https://stackbone.ai",
|
|
12377
|
+
"X-Title": "Stackbone Agent Platform"
|
|
12378
|
+
};
|
|
12262
12379
|
MODEL_PROXY_URL_ENV = "STACKBONE_MODEL_PROXY_URL";
|
|
12263
12380
|
MODEL_PROXY_TOKEN_ENV = "STACKBONE_MODEL_PROXY_TOKEN";
|
|
12264
12381
|
present$2 = (value) => value !== void 0 && value !== "";
|
|
@@ -15821,7 +15938,7 @@ function parseRewriteReply(content) {
|
|
|
15821
15938
|
let parsed;
|
|
15822
15939
|
try {
|
|
15823
15940
|
const raw = (content ?? "").trim();
|
|
15824
|
-
parsed = JSON.parse(JSON_FENCE.exec(raw)?.[1] ?? raw);
|
|
15941
|
+
parsed = JSON.parse(JSON_FENCE$1.exec(raw)?.[1] ?? raw);
|
|
15825
15942
|
} catch {
|
|
15826
15943
|
return null;
|
|
15827
15944
|
}
|
|
@@ -15852,12 +15969,12 @@ function interleave(lists, topK) {
|
|
|
15852
15969
|
}
|
|
15853
15970
|
return merged;
|
|
15854
15971
|
}
|
|
15855
|
-
var MAX_REWRITE_QUERIES, JSON_FENCE, REPLY_FORMAT, Rewriter;
|
|
15972
|
+
var MAX_REWRITE_QUERIES, JSON_FENCE$1, REPLY_FORMAT, Rewriter;
|
|
15856
15973
|
var init_rewriter = __esmMin((() => {
|
|
15857
15974
|
init_src$26();
|
|
15858
15975
|
init_result$1();
|
|
15859
15976
|
MAX_REWRITE_QUERIES = 3;
|
|
15860
|
-
JSON_FENCE = /^```(?:json)?([\s\S]*)```$/i;
|
|
15977
|
+
JSON_FENCE$1 = /^```(?:json)?([\s\S]*)```$/i;
|
|
15861
15978
|
REPLY_FORMAT = `Write at most ${MAX_REWRITE_QUERIES} queries. Reply with JSON only: {"queries": ["...", "..."]}.`;
|
|
15862
15979
|
Rewriter = class {
|
|
15863
15980
|
constructor(options) {
|
|
@@ -17045,7 +17162,7 @@ function mapApiError(raw, model) {
|
|
|
17045
17162
|
cause: raw
|
|
17046
17163
|
};
|
|
17047
17164
|
}
|
|
17048
|
-
var AiModule, ChatCompletionsNamespace, EmbeddingsNamespace, ImagesNamespace, ModelsNamespace, RerankNamespace, rerankResponseSchema,
|
|
17165
|
+
var AiModule, ChatCompletionsNamespace, EmbeddingsNamespace, ImagesNamespace, ModelsNamespace, RerankNamespace, rerankResponseSchema, OPENROUTER_KEY_MISSING_MESSAGE, DATA_URL_RE;
|
|
17049
17166
|
var init_ai = __esmMin((() => {
|
|
17050
17167
|
init_module_gate();
|
|
17051
17168
|
init_result$1();
|
|
@@ -17286,10 +17403,6 @@ var init_ai = __esmMin((() => {
|
|
|
17286
17403
|
index: z.number().int().nonnegative(),
|
|
17287
17404
|
relevance_score: z.number()
|
|
17288
17405
|
})) });
|
|
17289
|
-
STACKBONE_DEFAULT_HEADERS = {
|
|
17290
|
-
"HTTP-Referer": "https://stackbone.ai",
|
|
17291
|
-
"X-Title": "Stackbone Agent Platform"
|
|
17292
|
-
};
|
|
17293
17406
|
OPENROUTER_KEY_MISSING_MESSAGE = "No model provider is configured — set `MODEL_PROVIDER_API_KEY` in the container, pass `modelProviderKey` to `createClient({...})`, or configure it from the Studio \"Model provider\" screen.";
|
|
17294
17407
|
DATA_URL_RE = /^data:(image\/[a-zA-Z0-9.+-]+);base64,(.*)$/;
|
|
17295
17408
|
}));
|
|
@@ -18457,6 +18570,15 @@ function resolveBrowserbaseKeepAlive() {
|
|
|
18457
18570
|
return !(raw === "0" || raw === "false");
|
|
18458
18571
|
}
|
|
18459
18572
|
/**
|
|
18573
|
+
* On WSL, `chrome-launcher` rewrites `--user-data-dir` to a Windows UNC path even for the
|
|
18574
|
+
* Linux Chromium we pin; that Chromium cannot use it and never opens its debugging port
|
|
18575
|
+
* (STA3-109). A Linux `--user-data-dir` in `args` lands after it, and Chromium keeps the
|
|
18576
|
+
* last occurrence. Setting `userDataDir` instead does not help: it gets rewritten too.
|
|
18577
|
+
*/
|
|
18578
|
+
function isWsl() {
|
|
18579
|
+
return release().toLowerCase().includes("microsoft");
|
|
18580
|
+
}
|
|
18581
|
+
/**
|
|
18460
18582
|
* Resolve the Chrome/Chromium executable Stagehand's `chrome-launcher` should launch for a
|
|
18461
18583
|
* LOCAL browser. Precedence: an explicit `STACKBONE_BROWSER_EXECUTABLE_PATH`, else the
|
|
18462
18584
|
* workspace's Playwright Chromium (`playwright`'s `chromium.executablePath()`). Returns
|
|
@@ -18719,28 +18841,44 @@ function createStagehandBrowserFactory(options) {
|
|
|
18719
18841
|
} : void 0;
|
|
18720
18842
|
const initialState = options?.loadInitialStorageState ? await options.loadInitialStorageState().catch(() => void 0) : void 0;
|
|
18721
18843
|
const hasInitialState = initialState !== void 0 && isNonEmptyStorageState(initialState);
|
|
18844
|
+
const { Stagehand, CustomOpenAIClient } = await importStagehand();
|
|
18845
|
+
if (typeof CustomOpenAIClient !== "function") throw new Error(`browserTools: the installed @browserbasehq/stagehand does not export CustomOpenAIClient, which the browser tools need to reach the model provider. Install ${STAGEHAND_INSTALL_SPEC}.`);
|
|
18722
18846
|
const localBrowserLaunchOptions = {};
|
|
18847
|
+
let wslProfileDir;
|
|
18723
18848
|
if (!browserbase) {
|
|
18724
18849
|
if (cdpUrl) localBrowserLaunchOptions["cdpUrl"] = cdpUrl;
|
|
18725
|
-
if (
|
|
18850
|
+
if (!cdpUrl) localBrowserLaunchOptions["headless"] = !headed;
|
|
18726
18851
|
if (hasInitialState) localBrowserLaunchOptions["storageState"] = initialState;
|
|
18727
18852
|
if (!cdpUrl) {
|
|
18728
18853
|
const executablePath = await resolveChromeExecutablePath();
|
|
18729
18854
|
if (executablePath) localBrowserLaunchOptions["executablePath"] = executablePath;
|
|
18730
|
-
|
|
18855
|
+
const args = ["--no-sandbox", "--disable-dev-shm-usage"];
|
|
18856
|
+
if (isWsl()) {
|
|
18857
|
+
wslProfileDir = await mkdtemp(join(tmpdir(), "stackbone-chromium-"));
|
|
18858
|
+
args.push(`--user-data-dir=${wslProfileDir}`);
|
|
18859
|
+
}
|
|
18860
|
+
localBrowserLaunchOptions["args"] = args;
|
|
18731
18861
|
}
|
|
18732
18862
|
}
|
|
18733
|
-
const
|
|
18734
|
-
|
|
18735
|
-
|
|
18736
|
-
|
|
18737
|
-
|
|
18738
|
-
|
|
18863
|
+
const modelName = options?.modelName ?? DEFAULT_BROWSER_MODEL;
|
|
18864
|
+
const llmClient = new CustomOpenAIClient({
|
|
18865
|
+
modelName,
|
|
18866
|
+
client: callWithBrowserModel(new OpenAI({
|
|
18867
|
+
apiKey,
|
|
18868
|
+
baseURL,
|
|
18869
|
+
defaultHeaders: STACKBONE_DEFAULT_HEADERS
|
|
18870
|
+
}))
|
|
18871
|
+
});
|
|
18872
|
+
const removeWslProfileDir = () => wslProfileDir ? rm(wslProfileDir, {
|
|
18873
|
+
recursive: true,
|
|
18874
|
+
force: true
|
|
18875
|
+
}) : Promise.resolve();
|
|
18739
18876
|
const stagehand = new Stagehand(browserbase ? {
|
|
18740
18877
|
env: "BROWSERBASE",
|
|
18741
18878
|
apiKey: browserbase.apiKey,
|
|
18742
18879
|
projectId: browserbase.projectId,
|
|
18743
|
-
model,
|
|
18880
|
+
model: modelName,
|
|
18881
|
+
llmClient,
|
|
18744
18882
|
browserbaseSessionCreateParams: {
|
|
18745
18883
|
projectId: browserbase.projectId,
|
|
18746
18884
|
proxies: true,
|
|
@@ -18750,16 +18888,28 @@ function createStagehandBrowserFactory(options) {
|
|
|
18750
18888
|
}
|
|
18751
18889
|
} : {
|
|
18752
18890
|
env: "LOCAL",
|
|
18753
|
-
model,
|
|
18891
|
+
model: modelName,
|
|
18892
|
+
llmClient,
|
|
18754
18893
|
...Object.keys(localBrowserLaunchOptions).length > 0 ? { localBrowserLaunchOptions } : {}
|
|
18755
18894
|
});
|
|
18756
|
-
|
|
18895
|
+
try {
|
|
18896
|
+
await stagehand.init();
|
|
18897
|
+
} catch (err) {
|
|
18898
|
+
await removeWslProfileDir();
|
|
18899
|
+
throw err;
|
|
18900
|
+
}
|
|
18757
18901
|
const pageHandle = stagehand.context.activePage() ?? await stagehand.context.newPage();
|
|
18758
18902
|
if (hasInitialState) await injectStorageStateViaCDP(pageHandle, initialState);
|
|
18759
18903
|
const liveViewUrl = browserbase ? await resolveBrowserbaseLiveViewUrl(stagehand, browserbase, resolveLiveViewUrl) : void 0;
|
|
18760
18904
|
return {
|
|
18761
18905
|
page: adaptStagehandPage(stagehand, pageHandle),
|
|
18762
|
-
close: () =>
|
|
18906
|
+
close: async () => {
|
|
18907
|
+
try {
|
|
18908
|
+
await stagehand.close();
|
|
18909
|
+
} finally {
|
|
18910
|
+
await removeWslProfileDir();
|
|
18911
|
+
}
|
|
18912
|
+
},
|
|
18763
18913
|
isLive: async () => {
|
|
18764
18914
|
try {
|
|
18765
18915
|
return await probeStagehandLive(stagehand.context.activePage() ?? pageHandle);
|
|
@@ -18772,6 +18922,43 @@ function createStagehandBrowserFactory(options) {
|
|
|
18772
18922
|
} };
|
|
18773
18923
|
}
|
|
18774
18924
|
/**
|
|
18925
|
+
* Make `client`'s chat calls carry the model of the browser tool call in flight
|
|
18926
|
+
* ({@link withBrowserModel}), keeping the request's own model outside one, and hand back
|
|
18927
|
+
* a `json_object` reply in a Markdown code fence as the bare JSON ({@link unfenceJsonReplies}).
|
|
18928
|
+
* Patched on the `openai` client because `CustomOpenAIClient` writes its launch
|
|
18929
|
+
* `modelName` into every request body and runs a bare `JSON.parse` on the reply.
|
|
18930
|
+
*/
|
|
18931
|
+
function callWithBrowserModel(client) {
|
|
18932
|
+
const completions = client.chat.completions;
|
|
18933
|
+
const create = completions.create.bind(completions);
|
|
18934
|
+
completions.create = (async (body, options) => {
|
|
18935
|
+
const completion = await create({
|
|
18936
|
+
...body,
|
|
18937
|
+
model: browserModel.getStore() ?? body.model
|
|
18938
|
+
}, options);
|
|
18939
|
+
return body.response_format?.type === "json_object" ? unfenceJsonReplies(completion) : completion;
|
|
18940
|
+
});
|
|
18941
|
+
return client;
|
|
18942
|
+
}
|
|
18943
|
+
/**
|
|
18944
|
+
* Replace each choice's content that is one Markdown code fence with the JSON inside
|
|
18945
|
+
* it, in place. OpenRouter does not apply `json_object` to Anthropic models, so
|
|
18946
|
+
* `anthropic/claude-haiku-4.5` fences every JSON reply, which Stagehand's `JSON.parse`
|
|
18947
|
+
* rejects (STA3-120 D6). Anything else (a stream, a tool call, unfenced text) passes
|
|
18948
|
+
* through untouched.
|
|
18949
|
+
*/
|
|
18950
|
+
function unfenceJsonReplies(completion) {
|
|
18951
|
+
const choices = completion?.choices;
|
|
18952
|
+
if (!Array.isArray(choices)) return completion;
|
|
18953
|
+
for (const choice of choices) {
|
|
18954
|
+
const message = choice?.message;
|
|
18955
|
+
if (typeof message?.content !== "string") continue;
|
|
18956
|
+
const json = JSON_FENCE.exec(message.content.trim())?.[1];
|
|
18957
|
+
if (json !== void 0) message.content = json.trim();
|
|
18958
|
+
}
|
|
18959
|
+
return completion;
|
|
18960
|
+
}
|
|
18961
|
+
/**
|
|
18775
18962
|
* Resolve the Browserbase live-view URL for an initialized Stagehand instance
|
|
18776
18963
|
* (STA-140): read the session id it exposes, then hand it to `resolve` with the
|
|
18777
18964
|
* account creds. Returns `undefined` when the instance carries no session id — never
|
|
@@ -18790,10 +18977,10 @@ async function resolveBrowserbaseLiveViewUrl(stagehand, credentials, resolve) {
|
|
|
18790
18977
|
* the page, but the AI operations moved OFF the page onto the instance and now take
|
|
18791
18978
|
* POSITIONAL args (`act(instruction, opts)`, `extract(instruction, schema, opts)`,
|
|
18792
18979
|
* `observe(instruction, opts)`). The tools still speak the v2 options-object shape
|
|
18793
|
-
* (`act({ action
|
|
18794
|
-
*
|
|
18795
|
-
*
|
|
18796
|
-
*
|
|
18980
|
+
* (`act({ action })`, `extract({ instruction, schema })`), so this adapter translates —
|
|
18981
|
+
* keeping every tool body and its tests untouched across the v2→v3 change. Any other
|
|
18982
|
+
* option is forwarded verbatim as the trailing options object. The model is not one of
|
|
18983
|
+
* them: it rides the call's async context ({@link withBrowserModel}).
|
|
18797
18984
|
*/
|
|
18798
18985
|
function adaptStagehandPage(stagehand, initialPage) {
|
|
18799
18986
|
const livePage = () => stagehand.context.activePage() ?? initialPage;
|
|
@@ -18841,7 +19028,7 @@ function adaptStagehandPage(stagehand, initialPage) {
|
|
|
18841
19028
|
}
|
|
18842
19029
|
};
|
|
18843
19030
|
}
|
|
18844
|
-
var MAX_DOWNLOAD_BYTES, DOWNLOAD_STALL_MS, delay, DEFAULT_BROWSER_MODEL, BROWSERBASE_SESSION_TIMEOUT_SECONDS, BROWSERBASE_KEEP_ALIVE_ENV, HEADED_ENV, EXECUTABLE_PATH_ENV, PROBE_RETRY_DELAY_MS, BROWSERBASE_API_BASE$1, defaultResolveLiveViewUrl, defaultImportStagehand, readBrowserProviderSecrets;
|
|
19031
|
+
var MAX_DOWNLOAD_BYTES, DOWNLOAD_STALL_MS, browserModel, delay, DEFAULT_BROWSER_MODEL, BROWSERBASE_SESSION_TIMEOUT_SECONDS, BROWSERBASE_KEEP_ALIVE_ENV, HEADED_ENV, EXECUTABLE_PATH_ENV, PROBE_RETRY_DELAY_MS, BROWSERBASE_API_BASE$1, defaultResolveLiveViewUrl, defaultImportStagehand, readBrowserProviderSecrets, JSON_FENCE, STAGEHAND_PEER, STAGEHAND_INSTALL_SPEC;
|
|
18845
19032
|
var init_browser_tools = __esmMin((() => {
|
|
18846
19033
|
init_ambient_client();
|
|
18847
19034
|
init_browser_profile_state();
|
|
@@ -18915,6 +19102,7 @@ var init_browser_tools = __esmMin((() => {
|
|
|
18915
19102
|
} },
|
|
18916
19103
|
additionalProperties: false
|
|
18917
19104
|
});
|
|
19105
|
+
browserModel = globalThis[Symbol.for("stackbone.browserTool.modelStore")] ??= new AsyncLocalStorage();
|
|
18918
19106
|
delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
18919
19107
|
DEFAULT_BROWSER_MODEL = "anthropic/claude-haiku-4.5";
|
|
18920
19108
|
BROWSERBASE_SESSION_TIMEOUT_SECONDS = 3600;
|
|
@@ -18938,6 +19126,12 @@ var init_browser_tools = __esmMin((() => {
|
|
|
18938
19126
|
"@browserbasehq/stagehand"
|
|
18939
19127
|
);
|
|
18940
19128
|
readBrowserProviderSecrets = (names) => stackbone.secrets.getMany(names);
|
|
19129
|
+
JSON_FENCE = /^```(?:json)?[^\S\n]*\n([\s\S]*)```$/i;
|
|
19130
|
+
STAGEHAND_PEER = {
|
|
19131
|
+
pkg: "@browserbasehq/stagehand",
|
|
19132
|
+
version: "3.6.0"
|
|
19133
|
+
};
|
|
19134
|
+
STAGEHAND_INSTALL_SPEC = `${STAGEHAND_PEER.pkg}@${STAGEHAND_PEER.version}`;
|
|
18941
19135
|
}));
|
|
18942
19136
|
//#endregion
|
|
18943
19137
|
//#region ../../libs/sdk/src/runtime/console-capture.ts
|
|
@@ -22541,6 +22735,7 @@ var init_store_pool = __esmMin((() => {
|
|
|
22541
22735
|
}));
|
|
22542
22736
|
//#endregion
|
|
22543
22737
|
//#region ../../libs/runtime/workspace-runtime/src/lib/util/provider-from-model-id.ts
|
|
22738
|
+
init_src$26();
|
|
22544
22739
|
var GATEWAY_PREFIX = "openrouter";
|
|
22545
22740
|
/**
|
|
22546
22741
|
* The provider half of a `vendor/model` id, or null when the id does not name one.
|
|
@@ -22551,9 +22746,10 @@ var GATEWAY_PREFIX = "openrouter";
|
|
|
22551
22746
|
*/
|
|
22552
22747
|
var providerFromModelId = (modelId) => {
|
|
22553
22748
|
if (!modelId) return null;
|
|
22554
|
-
const
|
|
22749
|
+
const bare = unpinModelId(modelId)?.modelId ?? modelId;
|
|
22750
|
+
const slash = bare.indexOf("/");
|
|
22555
22751
|
if (slash <= 0) return null;
|
|
22556
|
-
const prefix =
|
|
22752
|
+
const prefix = bare.slice(0, slash);
|
|
22557
22753
|
return prefix === GATEWAY_PREFIX ? null : prefix;
|
|
22558
22754
|
};
|
|
22559
22755
|
//#endregion
|
|
@@ -22994,7 +23190,7 @@ var init_role = __esmMin((() => {
|
|
|
22994
23190
|
var STACKBONE_CLI_BUILD_VERSION, EMULATOR_CAPABILITY_ROUTES, EMULATOR_CAPABILITIES, buildContractResponse;
|
|
22995
23191
|
var init_contract = __esmMin((() => {
|
|
22996
23192
|
init_src$26();
|
|
22997
|
-
STACKBONE_CLI_BUILD_VERSION = "0.9.
|
|
23193
|
+
STACKBONE_CLI_BUILD_VERSION = "0.9.4";
|
|
22998
23194
|
EMULATOR_CAPABILITY_ROUTES = {
|
|
22999
23195
|
"database.postgres_direct": "/api/db",
|
|
23000
23196
|
"rag.basic": "/api/rag",
|
|
@@ -28309,7 +28505,7 @@ var init_error_envelopes = __esmMin((() => {
|
|
|
28309
28505
|
function componentRef(name) {
|
|
28310
28506
|
return { $ref: `#/components/schemas/${name}` };
|
|
28311
28507
|
}
|
|
28312
|
-
var foldOpenApiDefinitions, named, errorEnvelopeSchema, CATALOG_EXPOSURE_EXTENSION, excludeFromCatalog, isCatalogExclusion, confirmBeforeCalling, INVALID_ANNOTATION, readCatalogAnnotation, jsonBody, describedJsonBody, readOptionalJsonBody, describedJsonBodyOf, renderedSchema, referencedJsonBody, referencedJsonResponse, readOptionalJsonBodyAs, multipartBody, uploadedFile, jsonResponse$
|
|
28508
|
+
var foldOpenApiDefinitions, named, errorEnvelopeSchema, CATALOG_EXPOSURE_EXTENSION, excludeFromCatalog, isCatalogExclusion, confirmBeforeCalling, INVALID_ANNOTATION, readCatalogAnnotation, jsonBody, describedJsonBody, readOptionalJsonBody, describedJsonBodyOf, renderedSchema, referencedJsonBody, referencedJsonResponse, readOptionalJsonBodyAs, multipartBody, uploadedFile, jsonResponse$2, htmlResponse, errorResponse, forbiddenResponse, TARGET_NOUNS, invalidPayloadHook, repeatedQueryKeys;
|
|
28313
28509
|
var init_route_helpers = __esmMin((() => {
|
|
28314
28510
|
init_src$25();
|
|
28315
28511
|
init_error_boundary();
|
|
@@ -28421,7 +28617,7 @@ var init_route_helpers = __esmMin((() => {
|
|
|
28421
28617
|
format: "binary",
|
|
28422
28618
|
description
|
|
28423
28619
|
});
|
|
28424
|
-
jsonResponse$
|
|
28620
|
+
jsonResponse$2 = (schema, description) => ({
|
|
28425
28621
|
description,
|
|
28426
28622
|
content: { "application/json": { schema } }
|
|
28427
28623
|
});
|
|
@@ -28429,7 +28625,7 @@ var init_route_helpers = __esmMin((() => {
|
|
|
28429
28625
|
description,
|
|
28430
28626
|
content: { "text/html": { schema: z.string() } }
|
|
28431
28627
|
});
|
|
28432
|
-
errorResponse = (description) => jsonResponse$
|
|
28628
|
+
errorResponse = (description) => jsonResponse$2(errorEnvelopeSchema, description);
|
|
28433
28629
|
forbiddenResponse = (...capabilities) => errorResponse(`The caller's role ${capabilities.length === 1 ? `does not grant \`${capabilities[0]}\`` : `grants none of ${capabilities.map((capability) => `\`${capability}\``).join(", ")}`}. Only reachable on the browser-direct datapath — a proxy-signed call was authorized before it was signed.`);
|
|
28434
28630
|
TARGET_NOUNS = {
|
|
28435
28631
|
json: "body",
|
|
@@ -28490,27 +28686,27 @@ var init_vendor_surfaces = __esmMin((() => {
|
|
|
28490
28686
|
method: "get",
|
|
28491
28687
|
path: "/models",
|
|
28492
28688
|
description: "The workspace's deep agents as an OpenAI model list — one entry per registered agent, which is what a client's model dropdown reads. Chat never routes through here.",
|
|
28493
|
-
responses: { 200: jsonResponse$
|
|
28689
|
+
responses: { 200: jsonResponse$2(vendorPayload("An OpenAI `list` of `model` objects, one per registered deep agent."), "OpenAI's model list, with each agent's name as the model id.") }
|
|
28494
28690
|
}),
|
|
28495
28691
|
vendorRoute("openai", {
|
|
28496
28692
|
method: "post",
|
|
28497
28693
|
path: "/chat/completions",
|
|
28498
28694
|
description: "Run one turn on the deep agent named by `model`. Stateless by default — send the whole `messages` array, as you would to OpenAI — unless the call carries `x-stackbone-session` (durable thread) or `x-stackbone-conversation` (grouping only). x-stackbone-identities is a JSON string map of at most 32 literal fields with 1–100 character names and 1–500 character opaque values. Use ASCII Unicode escapes in HTTP headers. The user scalar supplies only customerId; contradictory copies return 400.",
|
|
28499
28695
|
request: { body: jsonBody(vendorPayload("An OpenAI Chat Completions request, with `model` naming the deep agent."), "OpenAI's Chat Completions request body.") },
|
|
28500
|
-
responses: { 200: jsonResponse$
|
|
28696
|
+
responses: { 200: jsonResponse$2(vendorPayload("OpenAI's `chat.completion` object."), streamedElsewhere("One `chat.completion`. A request that sets `\"stream\": true` is answered with `text/event-stream` instead: OpenAI's `chat.completion.chunk` frames, terminated by `data: [DONE]`.")) }
|
|
28501
28697
|
}),
|
|
28502
28698
|
vendorRoute("anthropic", {
|
|
28503
28699
|
method: "get",
|
|
28504
28700
|
path: "/models",
|
|
28505
28701
|
description: "The same catalog as the OpenAI surface's, in Anthropic's shape. Chat never routes through here.",
|
|
28506
|
-
responses: { 200: jsonResponse$
|
|
28702
|
+
responses: { 200: jsonResponse$2(vendorPayload("An Anthropic `data` list of model objects, one per registered deep agent."), "Anthropic's model list, with each agent's name as the model id.") }
|
|
28507
28703
|
}),
|
|
28508
28704
|
vendorRoute("anthropic", {
|
|
28509
28705
|
method: "post",
|
|
28510
28706
|
path: "/messages",
|
|
28511
28707
|
description: "Run one turn on the deep agent named by `model`. Same session and x-stackbone-identities JSON headers as the OpenAI surface, same statelessness by default. metadata.user_id supplies only customerId; contradictory copies return 400.",
|
|
28512
28708
|
request: { body: jsonBody(vendorPayload("An Anthropic Messages request, with `model` naming the deep agent."), "Anthropic's Messages request body.") },
|
|
28513
|
-
responses: { 200: jsonResponse$
|
|
28709
|
+
responses: { 200: jsonResponse$2(vendorPayload("Anthropic's `message` object."), streamedElsewhere("One `message`. A request that sets `\"stream\": true` is answered with `text/event-stream` instead: the `message_start` / `content_block_*` / `message_delta` / `message_stop` sequence.")) }
|
|
28514
28710
|
}),
|
|
28515
28711
|
vendorRoute("agui", {
|
|
28516
28712
|
method: "post",
|
|
@@ -29539,8 +29735,8 @@ var init_box_auth_gate = __esmMin((() => {
|
|
|
29539
29735
|
summary: "Prove a candidate deployment secret",
|
|
29540
29736
|
description: "Signed with the operator's candidate secret and answered against the box's own. Public by necessity: proving the secret is what happens BEFORE a valid control-plane signature can exist.",
|
|
29541
29737
|
responses: {
|
|
29542
|
-
200: jsonResponse$
|
|
29543
|
-
401: jsonResponse$
|
|
29738
|
+
200: jsonResponse$2(proxyChallengeResponseSchema, "The secret matched."),
|
|
29739
|
+
401: jsonResponse$2(proxyChallengeResponseSchema, "The secret did not match.")
|
|
29544
29740
|
}
|
|
29545
29741
|
}), async (c) => {
|
|
29546
29742
|
const url = new URL(c.req.url);
|
|
@@ -29570,11 +29766,11 @@ var init_box_auth_gate = __esmMin((() => {
|
|
|
29570
29766
|
description: "Signed with the same secret the challenge above proved. First registration binds the installation and organization; later calls must name the same pair. The MCP resource can change for that pair. The deployment row is written only after this call succeeds.",
|
|
29571
29767
|
request: { body: CONTROL_PLANE_IDENTITY_BODY },
|
|
29572
29768
|
responses: {
|
|
29573
|
-
200: jsonResponse$
|
|
29574
|
-
400: jsonResponse$
|
|
29575
|
-
401: jsonResponse$
|
|
29576
|
-
409: jsonResponse$
|
|
29577
|
-
500: jsonResponse$
|
|
29769
|
+
200: jsonResponse$2(z.object({ ok: z.literal(true) }), "The box recorded the identity."),
|
|
29770
|
+
400: jsonResponse$2(controlPlaneIdentityRefusalSchema, "The body did not name both ids as non-empty strings."),
|
|
29771
|
+
401: jsonResponse$2(controlPlaneIdentityRefusalSchema, "The signature did not verify against the box's own secret."),
|
|
29772
|
+
409: jsonResponse$2(controlPlaneIdentityRefusalSchema, "The box is already registered to a different organization or installation."),
|
|
29773
|
+
500: jsonResponse$2(controlPlaneIdentityRefusalSchema, "The box could not record the identity.")
|
|
29578
29774
|
}
|
|
29579
29775
|
}), async (c) => {
|
|
29580
29776
|
const url = new URL(c.req.url);
|
|
@@ -44594,7 +44790,7 @@ var identityPrincipal = (identity) => ({
|
|
|
44594
44790
|
*/
|
|
44595
44791
|
var BOX_SYSTEM_TOKEN = `sbsys_${randomBytes(32).toString("base64url")}`;
|
|
44596
44792
|
/** How an in-process self-call presents it. */
|
|
44597
|
-
var boxSystemAuthorization = () => `Bearer ${BOX_SYSTEM_TOKEN}`, signLiveKitToken;
|
|
44793
|
+
var boxSystemAuthorization = () => `Bearer ${BOX_SYSTEM_TOKEN}`, signLiveKitToken, signLiveKitInferenceToken;
|
|
44598
44794
|
var init_livekit_token = __esmMin((() => {
|
|
44599
44795
|
init_src$26();
|
|
44600
44796
|
signLiveKitToken = async (input) => {
|
|
@@ -44616,6 +44812,13 @@ var init_livekit_token = __esmMin((() => {
|
|
|
44616
44812
|
typ: "JWT"
|
|
44617
44813
|
}).setIssuer(input.apiKey).setSubject(input.identity).setNotBefore(now - 60).setExpirationTime(now + 300).sign(new TextEncoder().encode(input.apiSecret));
|
|
44618
44814
|
};
|
|
44815
|
+
signLiveKitInferenceToken = async (input) => {
|
|
44816
|
+
const now = input.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
44817
|
+
return new SignJWT({ inference: { perform: true } }).setProtectedHeader({
|
|
44818
|
+
alg: "HS256",
|
|
44819
|
+
typ: "JWT"
|
|
44820
|
+
}).setIssuer(input.apiKey).setSubject("stackbone-box").setNotBefore(now - 60).setExpirationTime(now + 600).sign(new TextEncoder().encode(input.apiSecret));
|
|
44821
|
+
};
|
|
44619
44822
|
}));
|
|
44620
44823
|
//#endregion
|
|
44621
44824
|
//#region ../../libs/runtime/workspace-runtime/src/lib/voice/call-token.ts
|
|
@@ -47802,7 +48005,7 @@ var SimulatedConnectorRegistry = class {
|
|
|
47802
48005
|
};
|
|
47803
48006
|
//#endregion
|
|
47804
48007
|
//#region ../../libs/runtime/workspace-runtime/src/lib/model-provider/provider-catalogues.ts
|
|
47805
|
-
var PROBE_TIMEOUT_MS$1, probeSource, readCatalogue, MAX_CONCURRENT_PROBES, fetchCatalogueSources, describePending, MAX_REASON_LENGTH, shortReason, createProviderCatalogues, orderedUnion, providerNames, notRespondingMessage, duplicatedModelIds, listServedModels, createServedModelsSnapshot;
|
|
48008
|
+
var PROBE_TIMEOUT_MS$1, probeSource, readCatalogue, MAX_CONCURRENT_PROBES, fetchCatalogueSources, describePending, MAX_REASON_LENGTH, shortReason, createProviderCatalogues, orderedUnion, providerNames, notRespondingMessage, duplicatedModelIds, claimsPlainModelIds, listServedModels, createServedModelsSnapshot;
|
|
47806
48009
|
var init_provider_catalogues = __esmMin((() => {
|
|
47807
48010
|
init_src$6();
|
|
47808
48011
|
init_src$18();
|
|
@@ -47837,6 +48040,11 @@ var init_provider_catalogues = __esmMin((() => {
|
|
|
47837
48040
|
}
|
|
47838
48041
|
};
|
|
47839
48042
|
readCatalogue = async ({ kind, baseUrl, apiKey }, fetchImpl) => {
|
|
48043
|
+
const fixed = fixedCatalogue(kind, baseUrl);
|
|
48044
|
+
if (fixed !== null) return {
|
|
48045
|
+
ok: true,
|
|
48046
|
+
models: [...fixed]
|
|
48047
|
+
};
|
|
47840
48048
|
const first = await fetchCatalogueSources(catalogueSources(kind, baseUrl), apiKey, fetchImpl);
|
|
47841
48049
|
const refused = first.find((response) => response.source.required && response.error !== void 0);
|
|
47842
48050
|
if (refused) return {
|
|
@@ -47942,6 +48150,7 @@ var init_provider_catalogues = __esmMin((() => {
|
|
|
47942
48150
|
providerIds
|
|
47943
48151
|
}));
|
|
47944
48152
|
};
|
|
48153
|
+
claimsPlainModelIds = (provider) => gatewayRulesFor(provider.kind).catalogue.fixedModelIds === null;
|
|
47945
48154
|
listServedModels = async (sources) => {
|
|
47946
48155
|
let providers;
|
|
47947
48156
|
try {
|
|
@@ -48121,8 +48330,57 @@ var init_answered_chat = __esmMin((() => {
|
|
|
48121
48330
|
};
|
|
48122
48331
|
}));
|
|
48123
48332
|
//#endregion
|
|
48333
|
+
//#region ../../libs/runtime/workspace-runtime/src/lib/model-provider/client/livekit-call.ts
|
|
48334
|
+
var LIVEKIT_RETRY_DELAYS_MS, MAX_RETRY_AFTER_MS, RETRIED_STATUSES, REGION_RESTRICTED_CODE, jsonResponse$1, liveKitCredentialsMissing, waitFor, sleep$2, explained, sendLiveKitCall;
|
|
48335
|
+
var init_livekit_call = __esmMin((() => {
|
|
48336
|
+
init_src$9();
|
|
48337
|
+
LIVEKIT_RETRY_DELAYS_MS = [250, 750];
|
|
48338
|
+
MAX_RETRY_AFTER_MS = 2e3;
|
|
48339
|
+
RETRIED_STATUSES = new Set([429, 503]);
|
|
48340
|
+
REGION_RESTRICTED_CODE = "inference_region_restricted";
|
|
48341
|
+
jsonResponse$1 = (status, body) => new Response(JSON.stringify(body), {
|
|
48342
|
+
status,
|
|
48343
|
+
headers: { "content-type": "application/json" }
|
|
48344
|
+
});
|
|
48345
|
+
liveKitCredentialsMissing = () => jsonResponse$1(503, openAiErrorBody("This model provider is LiveKit Inference, and the box has no LiveKit credentials to sign the call with. Save the LiveKit URL, API key and secret under Voice in Studio.", "api_error", "livekit_credentials_missing"));
|
|
48346
|
+
waitFor = (response, fallbackMs) => {
|
|
48347
|
+
const header = response.headers.get("retry-after");
|
|
48348
|
+
const seconds = header === null ? NaN : Number(header);
|
|
48349
|
+
if (!Number.isFinite(seconds) || seconds < 0) return fallbackMs;
|
|
48350
|
+
return Math.min(seconds * 1e3, MAX_RETRY_AFTER_MS);
|
|
48351
|
+
};
|
|
48352
|
+
sleep$2 = (ms, signal) => new Promise((resolve, reject) => {
|
|
48353
|
+
if (signal?.aborted) return reject(signal.reason);
|
|
48354
|
+
const timer = setTimeout(resolve, ms);
|
|
48355
|
+
signal?.addEventListener("abort", () => {
|
|
48356
|
+
clearTimeout(timer);
|
|
48357
|
+
reject(signal.reason);
|
|
48358
|
+
}, { once: true });
|
|
48359
|
+
});
|
|
48360
|
+
explained = async (response, modelId) => {
|
|
48361
|
+
if (response.status !== 403) return response;
|
|
48362
|
+
const text = await response.text();
|
|
48363
|
+
if (!text.includes(REGION_RESTRICTED_CODE)) return new Response(text, {
|
|
48364
|
+
status: response.status,
|
|
48365
|
+
headers: response.headers
|
|
48366
|
+
});
|
|
48367
|
+
return jsonResponse$1(403, openAiErrorBody(`LiveKit Inference refused the model \`${modelId ?? "unknown"}\`: it has no endpoint in this LiveKit project's data region. Pick a model with an endpoint there, or turn off the inference region restriction in the LiveKit project settings.`, "invalid_request_error", REGION_RESTRICTED_CODE));
|
|
48368
|
+
};
|
|
48369
|
+
sendLiveKitCall = async (input) => {
|
|
48370
|
+
const delays = input.delaysMs ?? LIVEKIT_RETRY_DELAYS_MS;
|
|
48371
|
+
const signal = input.init.signal ?? void 0;
|
|
48372
|
+
for (let attempt = 0;; attempt++) {
|
|
48373
|
+
const response = await input.fetchImpl(input.url, input.init);
|
|
48374
|
+
const delay = delays[attempt];
|
|
48375
|
+
if (!RETRIED_STATUSES.has(response.status) || delay === void 0) return explained(response, input.modelId);
|
|
48376
|
+
await response.body?.cancel();
|
|
48377
|
+
await sleep$2(waitFor(response, delay), signal);
|
|
48378
|
+
}
|
|
48379
|
+
};
|
|
48380
|
+
}));
|
|
48381
|
+
//#endregion
|
|
48124
48382
|
//#region ../../libs/runtime/workspace-runtime/src/lib/model-provider/client/model-provider-client.ts
|
|
48125
|
-
var ModelProviderUnreachableError, liveEnvironmentCredential, upstreamUrl, operationOf, isChatCompletion, ROUTED_OPERATIONS, PINNED_ONLY_OPERATIONS, takesPin, MODELS_PATH, modelIdOf, modelIdInBody, unpinRequest, readChatRequest, jsonResponse, notRespondingRefusal, routingRefusal, unreadablePinRefusal, createModelProviderClient;
|
|
48383
|
+
var ModelProviderUnreachableError, isLiveKitEndpoint, liveEnvironmentCredential, upstreamUrl, operationOf, isChatCompletion, ROUTED_OPERATIONS, PINNED_ONLY_OPERATIONS, takesPin, MODELS_PATH, modelIdOf, modelIdInBody, unpinRequest, readChatRequest, jsonResponse, notRespondingRefusal, routingRefusal, unreadablePinRefusal, createModelProviderClient;
|
|
48126
48384
|
var init_model_provider_client = __esmMin((() => {
|
|
48127
48385
|
init_src$9();
|
|
48128
48386
|
init_src$18();
|
|
@@ -48133,6 +48391,7 @@ var init_model_provider_client = __esmMin((() => {
|
|
|
48133
48391
|
init_proxy_paths();
|
|
48134
48392
|
init_error_message();
|
|
48135
48393
|
init_answered_chat();
|
|
48394
|
+
init_livekit_call();
|
|
48136
48395
|
ModelProviderUnreachableError = class extends Error {
|
|
48137
48396
|
constructor(provider, cause) {
|
|
48138
48397
|
super(messageOf$6(cause), { cause });
|
|
@@ -48140,6 +48399,7 @@ var init_model_provider_client = __esmMin((() => {
|
|
|
48140
48399
|
this.name = "ModelProviderUnreachableError";
|
|
48141
48400
|
}
|
|
48142
48401
|
};
|
|
48402
|
+
isLiveKitEndpoint = (endpoint, chatKind) => (chatKind ?? endpoint.kind ?? gatewayKindForBaseUrl(endpoint.baseUrl ?? "https://openrouter.ai/api/v1")) === "livekit";
|
|
48143
48403
|
liveEnvironmentCredential = () => resolveModelProviderCredential(void 0, process.env);
|
|
48144
48404
|
upstreamUrl = (baseUrl, path) => `${(baseUrl ?? "https://openrouter.ai/api/v1").replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
48145
48405
|
operationOf = (path) => path.replace(/^\/+/, "");
|
|
@@ -48279,14 +48539,29 @@ var init_model_provider_client = __esmMin((() => {
|
|
|
48279
48539
|
}
|
|
48280
48540
|
const body = chat === void 0 ? request.body : chat.body;
|
|
48281
48541
|
const headers = { ...request.headers };
|
|
48282
|
-
const
|
|
48283
|
-
if (
|
|
48284
|
-
|
|
48542
|
+
const liveKit = isLiveKitEndpoint(endpoint, chatKind);
|
|
48543
|
+
if (liveKit) {
|
|
48544
|
+
const token = await (options.liveKitToken?.() ?? Promise.resolve(null));
|
|
48545
|
+
if (token === null) return liveKitCredentialsMissing();
|
|
48546
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
48547
|
+
} else {
|
|
48548
|
+
const { apiKey } = endpoint;
|
|
48549
|
+
if (apiKey && apiKey.trim() !== "") headers["authorization"] = `Bearer ${apiKey}`;
|
|
48550
|
+
}
|
|
48551
|
+
const url = upstreamUrl(endpoint.baseUrl, path);
|
|
48552
|
+
const init = {
|
|
48285
48553
|
method: request.method ?? "GET",
|
|
48286
48554
|
headers,
|
|
48287
48555
|
...body !== void 0 ? { body } : {},
|
|
48288
48556
|
...request.signal ? { signal: request.signal } : {}
|
|
48289
|
-
}
|
|
48557
|
+
};
|
|
48558
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
48559
|
+
const upstream = liveKit ? await sendLiveKitCall({
|
|
48560
|
+
url,
|
|
48561
|
+
init,
|
|
48562
|
+
modelId: chat?.modelId,
|
|
48563
|
+
fetchImpl
|
|
48564
|
+
}) : await fetchImpl(url, init);
|
|
48290
48565
|
return chat === void 0 ? upstream : answeredChat(upstream);
|
|
48291
48566
|
};
|
|
48292
48567
|
/** Provider 1, the credential in effect: every call's path before STA3-17. */
|
|
@@ -48365,10 +48640,14 @@ var init_model_provider_client = __esmMin((() => {
|
|
|
48365
48640
|
}
|
|
48366
48641
|
if (providers.length < 2 || PINNED_ONLY_OPERATIONS.has(operation)) return fallback;
|
|
48367
48642
|
if (listsModels) return listedUnion(providers);
|
|
48643
|
+
const claiming = providers.filter(claimsPlainModelIds);
|
|
48644
|
+
const [sole, ...others] = claiming;
|
|
48645
|
+
if (sole !== void 0 && others.length === 0) return sole.id === "default" ? void 0 : sole;
|
|
48646
|
+
const pool = claiming.length === 0 ? providers : claiming;
|
|
48368
48647
|
const modelId = modelIdInBody(request.body);
|
|
48369
48648
|
if (modelId === void 0) return fallback;
|
|
48370
|
-
const resolution = await resolveRoute(
|
|
48371
|
-
return resolution.kind === "routed" ? resolution.provider : routingRefusal(resolution, modelId,
|
|
48649
|
+
const resolution = await resolveRoute(pool, modelId);
|
|
48650
|
+
return resolution.kind === "routed" ? resolution.provider : routingRefusal(resolution, modelId, pool);
|
|
48372
48651
|
};
|
|
48373
48652
|
/** Send a call where {@link route} put it: provider 1, a provider, or the box's refusal. */
|
|
48374
48653
|
const dispatch = async (path, request, routed) => {
|
|
@@ -49212,6 +49491,153 @@ var listModelProviderModels = async (opts, body, env = {}) => {
|
|
|
49212
49491
|
});
|
|
49213
49492
|
};
|
|
49214
49493
|
//#endregion
|
|
49494
|
+
//#region ../../libs/runtime/workspace-runtime/src/lib/voice/livekit-credentials.ts
|
|
49495
|
+
init_src$26();
|
|
49496
|
+
var [URL_NAME, API_KEY_NAME, API_SECRET_NAME] = RESERVED_LIVEKIT_SECRET_NAMES;
|
|
49497
|
+
/**
|
|
49498
|
+
* Names a row this box's key cannot open (never its value), so the log says
|
|
49499
|
+
* which one to save again. True when `err` is that failure.
|
|
49500
|
+
*/
|
|
49501
|
+
var warnUndecryptable = (scope, name) => console.warn(`[${scope}] ${name} cannot be opened with this box's key; save it again`);
|
|
49502
|
+
var warnIfUndecryptable = (scope, name, err) => {
|
|
49503
|
+
if (!(err instanceof SecretUndecryptableError)) return false;
|
|
49504
|
+
warnUndecryptable(scope, name);
|
|
49505
|
+
return true;
|
|
49506
|
+
};
|
|
49507
|
+
/**
|
|
49508
|
+
* The stored LiveKit credentials, read on EVERY call — no cache — or `null` when
|
|
49509
|
+
* any of the three is missing or empty. Half a credential set cannot open a
|
|
49510
|
+
* room, so it answers exactly what nothing stored answers.
|
|
49511
|
+
*
|
|
49512
|
+
* A row this box's key cannot open throws {@link SecretUndecryptableError},
|
|
49513
|
+
* after a warning that names the row (never its value): saving the credentials
|
|
49514
|
+
* again is the fix, and the caller tells the operator so. Any other failure
|
|
49515
|
+
* throws as it came.
|
|
49516
|
+
*/
|
|
49517
|
+
var readLiveKitCredentials = async (opts) => {
|
|
49518
|
+
const values = await readSystemSecrets(opts, RESERVED_LIVEKIT_SECRET_NAMES, (name) => warnUndecryptable("voice", name));
|
|
49519
|
+
const url = values.get(URL_NAME);
|
|
49520
|
+
const apiKey = values.get(API_KEY_NAME);
|
|
49521
|
+
const apiSecret = values.get(API_SECRET_NAME);
|
|
49522
|
+
if (!url || !apiKey || !apiSecret) return null;
|
|
49523
|
+
return {
|
|
49524
|
+
url,
|
|
49525
|
+
apiKey,
|
|
49526
|
+
apiSecret
|
|
49527
|
+
};
|
|
49528
|
+
};
|
|
49529
|
+
/** The secret naming the LiveKit outbound SIP trunk `placeCall` dials through (STA3-56). */
|
|
49530
|
+
var SIP_OUTBOUND_TRUNK_SECRET = "LIVEKIT_SIP_OUTBOUND_TRUNK_ID";
|
|
49531
|
+
/** The optional secret naming the number the callee sees; the trunk's own when absent. */
|
|
49532
|
+
var SIP_CALLER_ID_SECRET = "LIVEKIT_SIP_CALLER_ID";
|
|
49533
|
+
/**
|
|
49534
|
+
* The outbound trunk, read on every call, or `null` when no trunk id is saved.
|
|
49535
|
+
* Not reserved names, unlike the three credentials: a trunk id opens nothing on
|
|
49536
|
+
* its own and has no privileged screen yet, so it is read from an `is_system`
|
|
49537
|
+
* row or the operator's own secret of that name, whichever exists.
|
|
49538
|
+
*/
|
|
49539
|
+
var readSipOutboundConfig = async (opts) => {
|
|
49540
|
+
const values = await readSecretsAnyKind(opts, [SIP_OUTBOUND_TRUNK_SECRET, SIP_CALLER_ID_SECRET], (name) => warnUndecryptable("voice", name));
|
|
49541
|
+
const trunkId = values.get(SIP_OUTBOUND_TRUNK_SECRET)?.trim();
|
|
49542
|
+
if (!trunkId) return null;
|
|
49543
|
+
const callerId = values.get(SIP_CALLER_ID_SECRET)?.trim();
|
|
49544
|
+
return {
|
|
49545
|
+
trunkId,
|
|
49546
|
+
...callerId ? { callerId } : {}
|
|
49547
|
+
};
|
|
49548
|
+
};
|
|
49549
|
+
var voiceNotConfigured = (message) => ({ refusal: {
|
|
49550
|
+
status: 503,
|
|
49551
|
+
body: {
|
|
49552
|
+
code: "voice_not_configured",
|
|
49553
|
+
message
|
|
49554
|
+
}
|
|
49555
|
+
} });
|
|
49556
|
+
/**
|
|
49557
|
+
* {@link readLiveKitCredentials} for a voice route: the credentials, or the 503 to
|
|
49558
|
+
* answer without them. A row sealed by another box key is fixed the way a missing one
|
|
49559
|
+
* is, by saving the credentials again, so it answers the same 503; any other read
|
|
49560
|
+
* failure is the box's own and throws, to the generic 500.
|
|
49561
|
+
*/
|
|
49562
|
+
var lookUpLiveKitCredentials = async (opts) => {
|
|
49563
|
+
let credentials;
|
|
49564
|
+
try {
|
|
49565
|
+
credentials = await readLiveKitCredentials(opts);
|
|
49566
|
+
} catch (err) {
|
|
49567
|
+
if (!(err instanceof SecretUndecryptableError)) throw err;
|
|
49568
|
+
return voiceNotConfigured("This box cannot read the saved LiveKit credentials: they were saved with another key. Open the Voice provider screen and save them again.");
|
|
49569
|
+
}
|
|
49570
|
+
if (!credentials) return voiceNotConfigured("This box has no LiveKit credentials. Save the URL, API key and API secret on the Voice provider screen.");
|
|
49571
|
+
return { credentials };
|
|
49572
|
+
};
|
|
49573
|
+
/**
|
|
49574
|
+
* The screen's view: whether all three are saved, and the URL in clear.
|
|
49575
|
+
*
|
|
49576
|
+
* The key and the secret are asked after as EXISTENCE only, never decrypted:
|
|
49577
|
+
* this view never shows them, so opening them would be work done to throw away.
|
|
49578
|
+
* The URL is decrypted to be shown, and a URL row this box's key cannot open
|
|
49579
|
+
* (sealed by another box) reads as not configured instead of failing the screen
|
|
49580
|
+
* the operator needs to replace it from. Only that: a database or key failure
|
|
49581
|
+
* throws, because answering "not configured" would hide a configured box.
|
|
49582
|
+
*/
|
|
49583
|
+
var getVoiceProvider = async (opts) => {
|
|
49584
|
+
const [url, apiKeySet, apiSecretSet] = await Promise.all([
|
|
49585
|
+
readSystemSecret(opts, URL_NAME).catch((err) => {
|
|
49586
|
+
if (!warnIfUndecryptable("voice-provider", URL_NAME, err)) throw err;
|
|
49587
|
+
return null;
|
|
49588
|
+
}),
|
|
49589
|
+
systemSecretExists(opts, API_KEY_NAME),
|
|
49590
|
+
systemSecretExists(opts, API_SECRET_NAME)
|
|
49591
|
+
]);
|
|
49592
|
+
return {
|
|
49593
|
+
configured: url !== null && apiKeySet && apiSecretSet,
|
|
49594
|
+
url
|
|
49595
|
+
};
|
|
49596
|
+
};
|
|
49597
|
+
/**
|
|
49598
|
+
* Save all three as `is_system` rows, with the activity-log entry, in ONE
|
|
49599
|
+
* transaction: the box never holds a new URL beside an old secret, and never a
|
|
49600
|
+
* save the trail does not show. The entry names the URL alone.
|
|
49601
|
+
*/
|
|
49602
|
+
var setVoiceProvider = async (opts, body, actor = { userId: null }) => {
|
|
49603
|
+
await opts.db.transaction(async (tx) => {
|
|
49604
|
+
await seedSystemSecrets(opts, {
|
|
49605
|
+
[URL_NAME]: body.url,
|
|
49606
|
+
[API_KEY_NAME]: body.apiKey,
|
|
49607
|
+
[API_SECRET_NAME]: body.apiSecret
|
|
49608
|
+
}, tx);
|
|
49609
|
+
await opts.audit.record({
|
|
49610
|
+
action: "voice_provider.update",
|
|
49611
|
+
actorUserId: actor.userId,
|
|
49612
|
+
subjectType: "voice_provider",
|
|
49613
|
+
payload: { url: body.url }
|
|
49614
|
+
}, tx);
|
|
49615
|
+
});
|
|
49616
|
+
await notifySecretsChanged();
|
|
49617
|
+
};
|
|
49618
|
+
/**
|
|
49619
|
+
* Remove all three in ONE transaction, and answer the names actually removed.
|
|
49620
|
+
*
|
|
49621
|
+
* `cleared` comes off each DELETE's own answer, so a partial store records only
|
|
49622
|
+
* what it held, and a removal that found nothing records nothing — the rule
|
|
49623
|
+
* `removeModelProvider` keeps.
|
|
49624
|
+
*/
|
|
49625
|
+
var clearVoiceProvider = async (opts, actor = { userId: null }) => {
|
|
49626
|
+
const cleared = await opts.db.transaction(async (tx) => {
|
|
49627
|
+
const removed = [];
|
|
49628
|
+
for (const name of RESERVED_LIVEKIT_SECRET_NAMES) if (await deleteSystemSecret(opts, name, tx)) removed.push(name);
|
|
49629
|
+
if (removed.length > 0) await opts.audit.record({
|
|
49630
|
+
action: "voice_provider.delete",
|
|
49631
|
+
actorUserId: actor.userId,
|
|
49632
|
+
subjectType: "voice_provider",
|
|
49633
|
+
payload: { cleared: removed }
|
|
49634
|
+
}, tx);
|
|
49635
|
+
return removed;
|
|
49636
|
+
});
|
|
49637
|
+
if (cleared.length > 0) await notifySecretsChanged();
|
|
49638
|
+
return cleared;
|
|
49639
|
+
};
|
|
49640
|
+
//#endregion
|
|
49215
49641
|
//#region ../../libs/runtime/workspace-runtime/src/lib/model-provider/provider-registry-store.ts
|
|
49216
49642
|
init_src$26();
|
|
49217
49643
|
init_src$18();
|
|
@@ -49234,6 +49660,14 @@ var assertExtraProviderId = (id) => {
|
|
|
49234
49660
|
if (id === "default") throw new ProviderStoreError("default_provider", id);
|
|
49235
49661
|
if (!MODEL_PROVIDER_ID_PATTERN.test(id)) throw new ProviderStoreError("invalid_id", id);
|
|
49236
49662
|
};
|
|
49663
|
+
/**
|
|
49664
|
+
* Refuses a hand-made LiveKit provider: it is derived from the Voice credentials
|
|
49665
|
+
* (PLA-2416), and a second one would carry no credential of its own. Removal
|
|
49666
|
+
* stays open, so a row saved before the derivation can still be cleared.
|
|
49667
|
+
*/
|
|
49668
|
+
var assertNotVoiceProvider = (id, kind) => {
|
|
49669
|
+
if (id === "livekit" || kind === "livekit") throw new ProviderStoreError("voice_provider", id);
|
|
49670
|
+
};
|
|
49237
49671
|
var PROVIDER_SUFFIX = "__PROVIDER";
|
|
49238
49672
|
/**
|
|
49239
49673
|
* The two rows of an extra provider. The key is a row of its own holding the
|
|
@@ -49313,14 +49747,41 @@ var listExtraProviders = async (opts) => {
|
|
|
49313
49747
|
return (await Promise.all(rowNames.filter((name) => name.endsWith(PROVIDER_SUFFIX)).map((name) => readExtraProvider(opts, name)))).filter((provider) => provider !== null);
|
|
49314
49748
|
};
|
|
49315
49749
|
/**
|
|
49750
|
+
* LiveKit Inference, while the box holds its Voice credentials (PLA-2416): the
|
|
49751
|
+
* endpoint is fixed and the call is signed with those credentials, so there is
|
|
49752
|
+
* nothing to store and nothing an operator could get wrong. Credentials the box
|
|
49753
|
+
* cannot read leave it out rather than failing the list every model call reads.
|
|
49754
|
+
*/
|
|
49755
|
+
var readVoiceProvider = async (opts) => {
|
|
49756
|
+
try {
|
|
49757
|
+
if (await readLiveKitCredentials(opts) === null) return null;
|
|
49758
|
+
} catch (err) {
|
|
49759
|
+
console.warn(`[model-provider] LiveKit Inference left out, Voice credentials unreadable: ${String(err)}`);
|
|
49760
|
+
return null;
|
|
49761
|
+
}
|
|
49762
|
+
return {
|
|
49763
|
+
id: LIVEKIT_MODEL_PROVIDER_ID,
|
|
49764
|
+
name: "LiveKit Inference",
|
|
49765
|
+
kind: "livekit",
|
|
49766
|
+
baseUrl: LIVEKIT_INFERENCE_BASE_URL,
|
|
49767
|
+
apiKey: void 0,
|
|
49768
|
+
source: "voice"
|
|
49769
|
+
};
|
|
49770
|
+
};
|
|
49771
|
+
/**
|
|
49316
49772
|
* Every configured provider, provider 1 first, then the extras in the order
|
|
49317
|
-
* they were added.
|
|
49773
|
+
* they were added, then LiveKit Inference when Voice is configured.
|
|
49318
49774
|
*
|
|
49319
49775
|
* `env` is the boot snapshot, taken before boot seeded the stored credential
|
|
49320
49776
|
* onto `process.env` (see `resolveModelProviderSource`).
|
|
49321
49777
|
*/
|
|
49322
49778
|
var listProviders = async (opts, env = {}) => {
|
|
49323
|
-
const [first,
|
|
49779
|
+
const [first, stored, voice] = await Promise.all([
|
|
49780
|
+
readDefaultProvider(opts, env),
|
|
49781
|
+
listExtraProviders(opts),
|
|
49782
|
+
readVoiceProvider(opts)
|
|
49783
|
+
]);
|
|
49784
|
+
const extras = voice === null ? stored : [...stored.filter((p) => p.id !== voice.id), voice];
|
|
49324
49785
|
return first === null ? extras : [first, ...extras];
|
|
49325
49786
|
};
|
|
49326
49787
|
/**
|
|
@@ -49362,11 +49823,13 @@ var writeProvider = async (opts, id, input, action, actor) => {
|
|
|
49362
49823
|
var providerExists = (opts, id) => systemSecretExists(opts, rowNamesOf(id).provider);
|
|
49363
49824
|
var addProvider = async (opts, provider, actor = { userId: null }) => {
|
|
49364
49825
|
assertExtraProviderId(provider.id);
|
|
49826
|
+
assertNotVoiceProvider(provider.id, provider.kind);
|
|
49365
49827
|
if (await providerExists(opts, provider.id)) throw new ProviderStoreError("duplicate", provider.id);
|
|
49366
49828
|
await writeProvider(opts, provider.id, provider, "model_provider.create", actor);
|
|
49367
49829
|
};
|
|
49368
49830
|
var updateProvider = async (opts, id, input, actor = { userId: null }) => {
|
|
49369
49831
|
assertExtraProviderId(id);
|
|
49832
|
+
assertNotVoiceProvider(id, input.kind);
|
|
49370
49833
|
if (!await providerExists(opts, id)) throw new ProviderStoreError("not_found", id);
|
|
49371
49834
|
await writeProvider(opts, id, input, "model_provider.update", actor);
|
|
49372
49835
|
};
|
|
@@ -49398,6 +49861,40 @@ var removeProvider = async (opts, id, actor = { userId: null }) => {
|
|
|
49398
49861
|
await notifySecretsChanged();
|
|
49399
49862
|
};
|
|
49400
49863
|
//#endregion
|
|
49864
|
+
//#region ../../libs/runtime/workspace-runtime/src/lib/model-provider/client/livekit-inference-auth.ts
|
|
49865
|
+
init_livekit_token();
|
|
49866
|
+
/**
|
|
49867
|
+
* Signs a new token this long before the cached one expires, so a call that
|
|
49868
|
+
* starts on a token never reaches LiveKit with it already expired.
|
|
49869
|
+
*/
|
|
49870
|
+
var REFRESH_MARGIN_SECONDS = 60;
|
|
49871
|
+
/**
|
|
49872
|
+
* The bearer for LiveKit Inference (PLA-2416), or `null` when the box has no
|
|
49873
|
+
* LiveKit credentials. One token is reused until it nears expiry, and a new one
|
|
49874
|
+
* is signed when the key or the secret changes.
|
|
49875
|
+
*/
|
|
49876
|
+
var createLiveKitInferenceAuth = (options) => {
|
|
49877
|
+
const now = options.nowSeconds ?? (() => Math.floor(Date.now() / 1e3));
|
|
49878
|
+
let cached = null;
|
|
49879
|
+
return async () => {
|
|
49880
|
+
const credentials = await options.credentials();
|
|
49881
|
+
if (credentials === null) return null;
|
|
49882
|
+
const signedWith = `${credentials.apiKey}\u0000${credentials.apiSecret}`;
|
|
49883
|
+
const at = now();
|
|
49884
|
+
if (cached?.signedWith === signedWith && cached.expiresAt - REFRESH_MARGIN_SECONDS > at) return cached.token;
|
|
49885
|
+
const token = await signLiveKitInferenceToken({
|
|
49886
|
+
...credentials,
|
|
49887
|
+
nowSeconds: at
|
|
49888
|
+
});
|
|
49889
|
+
cached = {
|
|
49890
|
+
signedWith,
|
|
49891
|
+
token,
|
|
49892
|
+
expiresAt: at + 600
|
|
49893
|
+
};
|
|
49894
|
+
return token;
|
|
49895
|
+
};
|
|
49896
|
+
};
|
|
49897
|
+
//#endregion
|
|
49401
49898
|
//#region ../../libs/runtime/workspace-runtime/src/lib/otlp/otlp-config.ts
|
|
49402
49899
|
var CAPTURE_MODES = [
|
|
49403
49900
|
"NO_CONTENT",
|
|
@@ -49585,7 +50082,8 @@ var createMountContext = (opts, hooks, app) => {
|
|
|
49585
50082
|
}),
|
|
49586
50083
|
storedKind: () => readStoredModelProviderKind(secrets),
|
|
49587
50084
|
providers: () => listProviders(secrets, bootEnv),
|
|
49588
|
-
catalogues: opts.providerCatalogues
|
|
50085
|
+
catalogues: opts.providerCatalogues,
|
|
50086
|
+
liveKitToken: createLiveKitInferenceAuth({ credentials: () => readLiveKitCredentials(secrets) })
|
|
49589
50087
|
}),
|
|
49590
50088
|
servedModels: opts.servedModels ?? (() => null),
|
|
49591
50089
|
simulatedConnectors: new SimulatedConnectorRegistry({ runs: opts.invocations }),
|
|
@@ -56154,7 +56652,7 @@ function untilAborted(work, signal) {
|
|
|
56154
56652
|
* download behind it; `return()` alone waits for a chunk a stalled stream never
|
|
56155
56653
|
* sends.
|
|
56156
56654
|
*/
|
|
56157
|
-
function release(data, iterator) {
|
|
56655
|
+
function release$1(data, iterator) {
|
|
56158
56656
|
data.destroy?.();
|
|
56159
56657
|
Promise.resolve(iterator.return?.()).catch(() => void 0);
|
|
56160
56658
|
}
|
|
@@ -56181,7 +56679,7 @@ async function bytesOf({ fileName, data }, maxBytes, signal) {
|
|
|
56181
56679
|
chunks.push(next.value);
|
|
56182
56680
|
}
|
|
56183
56681
|
} catch (err) {
|
|
56184
|
-
release(data, iterator);
|
|
56682
|
+
release$1(data, iterator);
|
|
56185
56683
|
throw err;
|
|
56186
56684
|
}
|
|
56187
56685
|
return Buffer.concat(chunks, length);
|
|
@@ -72207,7 +72705,7 @@ var createApprovalsRouter = (deps) => {
|
|
|
72207
72705
|
description: "What the SDK calls to pause on a human. The box records the row, publishes `approval.created` on its event bus so an open inbox repaints, and answers with the callback URL the decision will be POSTed back to. Repeat calls carrying the same `idempotencyKey` return the SAME approval rather than raising a second one.",
|
|
72208
72706
|
request: { body: jsonBody(named("CreateApprovalRequest", createApprovalRequestSchema), "The topic, the callback path the decision is delivered to, and the payload a reviewer reads.") },
|
|
72209
72707
|
responses: {
|
|
72210
|
-
201: jsonResponse$
|
|
72708
|
+
201: jsonResponse$2(named("ApprovalCreatedResponse", approvalCreatedResponseSchema), "The approval id, its callback URL and when it expires."),
|
|
72211
72709
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
72212
72710
|
500: errorResponse("The store refused the row (`internal_error`), message verbatim.")
|
|
72213
72711
|
}
|
|
@@ -72248,7 +72746,7 @@ var createApprovalsRouter = (deps) => {
|
|
|
72248
72746
|
description: "Cursor-paginated, newest first unless `order=asc`, filterable by status, topic and the run that raised them. The cursor is opaque and rewinds to before/after the last id at that timestamp.",
|
|
72249
72747
|
request: { query: listApprovalsQuery },
|
|
72250
72748
|
responses: {
|
|
72251
|
-
200: jsonResponse$
|
|
72749
|
+
200: jsonResponse$2(named("ApprovalListResponse", approvalListResponseSchema), "One page of approvals, and the cursors either side of it."),
|
|
72252
72750
|
400: errorResponse("A filter this inbox cannot honour — an unknown `status`, a `runId` that is not a uuid, a `limit` outside [1, 100] (`invalid_payload`) — or a cursor this box did not issue (`invalid_cursor`).")
|
|
72253
72751
|
}
|
|
72254
72752
|
}), async (c) => {
|
|
@@ -72306,7 +72804,7 @@ var createApprovalsRouter = (deps) => {
|
|
|
72306
72804
|
description: "The row plus its audit trail: every decision recorded against it, including the one a timeout sweep wrote. A pending approval carries an empty `decisions` array, never a missing one.",
|
|
72307
72805
|
request: { params: approvalIdPath },
|
|
72308
72806
|
responses: {
|
|
72309
|
-
200: jsonResponse$
|
|
72807
|
+
200: jsonResponse$2(named("ApprovalDetailResponse", approvalDetailResponseSchema), "The approval and its decisions, oldest first."),
|
|
72310
72808
|
404: errorResponse("No such approval (`approval_not_found`) — or an id that is not an id at all.")
|
|
72311
72809
|
}
|
|
72312
72810
|
}), async (c) => {
|
|
@@ -72327,7 +72825,7 @@ var createApprovalsRouter = (deps) => {
|
|
|
72327
72825
|
body: jsonBody(named("DecideApprovalRequest", decideApprovalRequestSchema), "The decision, plus the optional edited payload and reviewer comment that ride with it.")
|
|
72328
72826
|
},
|
|
72329
72827
|
responses: {
|
|
72330
|
-
200: jsonResponse$
|
|
72828
|
+
200: jsonResponse$2(named("DecideApprovalResponse", decideApprovalResponseSchema), "The approval as it now stands, and the decision recorded against it."),
|
|
72331
72829
|
400: errorResponse("The body failed its schema, or an `editedPayload` was sent for a pause holding more than one tool call (`invalid_payload`)."),
|
|
72332
72830
|
403: forbiddenResponse("hitl:decide"),
|
|
72333
72831
|
404: errorResponse("No such approval (`approval_not_found`)."),
|
|
@@ -72596,7 +73094,7 @@ var createActivityRouter = (deps) => {
|
|
|
72596
73094
|
description: "The append-only trail of what humans did on THIS box — an approval decided, the creator's config saved or rolled back, a secret rotated. One SOURCE of the organization's activity log: the control plane serves its own rows separately and the Activity screen merges the two, so every page names the source it came from and how completely that source answered. `action` and `resourceType` are matched as OPEN strings, never against this build's catalog — a box on a newer image records actions an older bundle has never heard of, and dropping those rows would be the silent omission the log exists to prevent. Pages are walked with `nextCursor`. There is no write verb: the table refuses UPDATE and DELETE below every application.",
|
|
72597
73095
|
request: { query: auditQuerySchema },
|
|
72598
73096
|
responses: {
|
|
72599
|
-
200: jsonResponse$
|
|
73097
|
+
200: jsonResponse$2(named("AuditPage", auditPageSchema), "One page of this source's rows, the cursor the next page is asked with (`null` on the last), and how completely this source answered."),
|
|
72600
73098
|
400: errorResponse("The query string failed its schema (`invalid_payload` — an unparseable bound, or a `limit` past the contract ceiling), or the cursor is not one this box minted (`invalid_cursor`). An unreadable cursor is REFUSED rather than answered with the first page: a caller handed page one for a cursor it cannot advance pages forever."),
|
|
72601
73099
|
403: forbiddenResponse("activity:read")
|
|
72602
73100
|
}
|
|
@@ -72746,7 +73244,7 @@ var createBrowserProfilesRouter = (deps) => {
|
|
|
72746
73244
|
summary: "List the stored browser sessions",
|
|
72747
73245
|
description: "METADATA ONLY, newest refresh first. The stored session state is a serialized Playwright `storageState` — credential material — and no route on this box answers it, so a row here carries the origin, the identity, whether a re-login is pending and when it was last captured, and nothing else.",
|
|
72748
73246
|
responses: {
|
|
72749
|
-
200: jsonResponse$
|
|
73247
|
+
200: jsonResponse$2(named("StudioBrowserProfileListResponse", studioBrowserProfileListResponseSchema), "Every stored profile, as metadata."),
|
|
72750
73248
|
403: forbiddenResponse("secrets:rotate")
|
|
72751
73249
|
}
|
|
72752
73250
|
}), async (c) => c.json(await listBrowserProfiles(browserProfilesOptions), 200));
|
|
@@ -72760,7 +73258,7 @@ var createBrowserProfilesRouter = (deps) => {
|
|
|
72760
73258
|
description: "Deletes the encrypted session for one `origin` + `identity`, so the agent has to log in again. The profile is named in the QUERY STRING, not in a body.",
|
|
72761
73259
|
request: { query: revokeQuery },
|
|
72762
73260
|
responses: {
|
|
72763
|
-
200: jsonResponse$
|
|
73261
|
+
200: jsonResponse$2(named("StudioBrowserProfileDeleteResponse", studioBrowserProfileDeleteResponseSchema), "The profile is gone."),
|
|
72764
73262
|
403: forbiddenResponse("secrets:rotate"),
|
|
72765
73263
|
404: errorResponse("No stored session for that origin — or no origin was named at all.")
|
|
72766
73264
|
}
|
|
@@ -73146,7 +73644,7 @@ var createBrowserProviderRouter = (secretsOptions) => {
|
|
|
73146
73644
|
summary: "Read the browser provider configuration",
|
|
73147
73645
|
description: "The stored mode and its identifiers in clear, the API key masked, plus the mode a launch would actually open right now and any `BROWSER_*` variable in the box's environment that outranks the stored value. `effective_mode` is null when the configuration is unsatisfiable, and `effective_error` names the missing field.",
|
|
73148
73646
|
responses: {
|
|
73149
|
-
200: jsonResponse$
|
|
73647
|
+
200: jsonResponse$2(named("BrowserProviderGetResponse", browserProviderGetResponseSchema), "The stored configuration and the mode it resolves to."),
|
|
73150
73648
|
403: forbiddenResponse("secrets:rotate")
|
|
73151
73649
|
}
|
|
73152
73650
|
}), async (c) => c.json(await getBrowserProvider(secretsOptions), 200));
|
|
@@ -73159,7 +73657,7 @@ var createBrowserProviderRouter = (secretsOptions) => {
|
|
|
73159
73657
|
description: "REPLACES the whole configuration — a field omitted or empty is deleted from the store, which is how the API key is cleared. Cross-field requirements (\"browserbase needs a project id\") are deliberately not refused here: an unsatisfiable mode surfaces on the read as `effective_error`, so a half-finished save is still savable.",
|
|
73160
73658
|
request: { body: jsonBody(browserProviderRequestBody, "The mode and the fields that mode needs.") },
|
|
73161
73659
|
responses: {
|
|
73162
|
-
200: jsonResponse$
|
|
73660
|
+
200: jsonResponse$2(named("BrowserProviderGetResponse", browserProviderGetResponseSchema), "The fresh masked view, including the recomputed effective mode."),
|
|
73163
73661
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
73164
73662
|
403: forbiddenResponse("secrets:rotate")
|
|
73165
73663
|
}
|
|
@@ -73176,7 +73674,7 @@ var createBrowserProviderRouter = (secretsOptions) => {
|
|
|
73176
73674
|
description: "Opens (or asks after) the browser the body describes, WITHOUT saving anything. A configuration that cannot be reached rides back as `ok: false` DATA with a reason the operator can act on — never as an error status.",
|
|
73177
73675
|
request: { body: jsonBody(browserProviderRequestBody, "The candidate configuration to probe. Same shape as the PUT body.") },
|
|
73178
73676
|
responses: {
|
|
73179
|
-
200: jsonResponse$
|
|
73677
|
+
200: jsonResponse$2(named("BrowserProviderTestResponse", browserProviderTestResponseSchema), "Whether the candidate configuration answered, and why not when it did not."),
|
|
73180
73678
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
73181
73679
|
403: forbiddenResponse("secrets:rotate")
|
|
73182
73680
|
}
|
|
@@ -73611,7 +74109,7 @@ var createCatalogRouter = (deps) => {
|
|
|
73611
74109
|
tags: [CATALOG_TAG],
|
|
73612
74110
|
summary: "List everything this workspace serves",
|
|
73613
74111
|
description: "Every deep agent and every workflow the box hosts, of both kinds, in one normalized shape and one total order (name, then kind). Platform entries are RETURNED and flagged `system: true`, never filtered out. Both halves are resolved on the read, so an agent hot-added by `stackbone dev` and a workflow set swapped by a reload both appear with no restart — and a row degraded only because nobody has published the prompt it declares goes healthy on the next read.",
|
|
73614
|
-
responses: { 200: jsonResponse$
|
|
74112
|
+
responses: { 200: jsonResponse$2(named("WorkspaceCatalogListResponse", workspaceCatalogListResponseSchema), "Every entry this workspace serves. A row is legitimately PARTIAL: `metadata`, `reason`, `prompts`, `models` and `outputs` are each absent or null on an entry the build recorded nothing about, which is a different answer from an empty one.") }
|
|
73615
74113
|
}), async (c) => c.json(workspaceCatalogListResponseSchema.parse({ items: await currentEntries() }), 200));
|
|
73616
74114
|
app.openapi(createRoute({
|
|
73617
74115
|
method: "get",
|
|
@@ -73619,7 +74117,7 @@ var createCatalogRouter = (deps) => {
|
|
|
73619
74117
|
tags: [CATALOG_TAG],
|
|
73620
74118
|
summary: "Summarise the last week of runs, per entry",
|
|
73621
74119
|
description: `For every entry the list above returns: when it last ran, and how its runs over the last 7 days came out. The window is FIXED, not a query param — it is echoed on the response so a screen labels what it shows, and a caller-chosen one would let two surfaces publish different "recent activity" for the same entry. Entry-driven by construction: an entry that has never run comes back with zeros rather than vanishing.`,
|
|
73622
|
-
responses: { 200: jsonResponse$
|
|
74120
|
+
responses: { 200: jsonResponse$2(named("CatalogRunAggregatesResponse", catalogRunAggregatesResponseSchema), "One row per catalog entry, plus the window every row was counted over.") }
|
|
73623
74121
|
}), async (c) => {
|
|
73624
74122
|
const since = (/* @__PURE__ */ new Date(Date.now() - 7 * DAY_MS$1)).toISOString();
|
|
73625
74123
|
const items = await deps.invocations.aggregateRunsByEntity({
|
|
@@ -73645,8 +74143,8 @@ var createCatalogRouter = (deps) => {
|
|
|
73645
74143
|
description: "The static diagram of the workflow's control flow, as the build drew it from the source. A known workflow with no diagram to show answers `{ diagram: null }` — the bundle predates the scan, or carries a diagram this box cannot read. Not part of `GET /api/catalog`, which every screen reads: a diagram weighs tens of kilobytes.",
|
|
73646
74144
|
request: { params: workflowNamePath },
|
|
73647
74145
|
responses: {
|
|
73648
|
-
200: jsonResponse$
|
|
73649
|
-
404: jsonResponse$
|
|
74146
|
+
200: jsonResponse$2(named("WorkflowDiagramResponse", workflowDiagramResponseSchema), "The diagram, or `null` when there is none to show."),
|
|
74147
|
+
404: jsonResponse$2(workflowNotFoundBodySchema, "No workflow by that name.")
|
|
73650
74148
|
}
|
|
73651
74149
|
}), (c) => {
|
|
73652
74150
|
const { name } = c.req.valid("param");
|
|
@@ -74077,7 +74575,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74077
74575
|
summary: "List the workspace prompts",
|
|
74078
74576
|
description: "Every prompt this box holds, of every owner, with the PUBLISHED text resolved into `content` — so a list read shows what each owner actually runs, not the newest draft somebody saved. `content` is `null` on a prompt with nothing published, which is a different answer from the prompt not existing. Capped at 200 rows, ordered by owner then key.\n\nGated by `config:write` even though it is a read: a prompt body is authored content, and the seats that may not edit one were never shown it either.",
|
|
74079
74577
|
responses: {
|
|
74080
|
-
200: jsonResponse$
|
|
74578
|
+
200: jsonResponse$2(named("StudioPromptListResponse", studioPromptListResponseSchema), "Every prompt on this box, published text resolved."),
|
|
74081
74579
|
403: forbiddenResponse("config:write")
|
|
74082
74580
|
}
|
|
74083
74581
|
}), async (c) => {
|
|
@@ -74093,7 +74591,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74093
74591
|
description: "Writes a new prompt and PUBLISHES its version 1 — version 1 is the only version there is, so leaving it unpublished would mean every new prompt starts as text nothing runs and no read returns. The write/publish split earns its keep from the second version onwards. An `agent` owner is rebuilt after the write, because a deep agent bakes its instruction into its graph and would otherwise keep answering with nothing.\n\nThis is the ONE route that carries the owner in the BODY; every other route carries it in the path. Neither half has a default: a create that guessed an owner would put the prompt somewhere no read will look.",
|
|
74094
74592
|
request: { body: jsonBody(named("CreateStudioPromptRequest", createStudioPromptRequestSchema), "The owner, the key, the display name and the template text.") },
|
|
74095
74593
|
responses: {
|
|
74096
|
-
200: jsonResponse$
|
|
74594
|
+
200: jsonResponse$2(named("StudioPrompt", studioPromptSchema), "The prompt as it now stands, publishing version 1."),
|
|
74097
74595
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
74098
74596
|
403: forbiddenResponse("config:write"),
|
|
74099
74597
|
404: errorResponse("The row was written and then could not be read back (`studio_prompt_not_found`) — a concurrent delete, not a bad request."),
|
|
@@ -74170,7 +74668,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74170
74668
|
query: promptVersionQuery
|
|
74171
74669
|
},
|
|
74172
74670
|
responses: {
|
|
74173
|
-
200: jsonResponse$
|
|
74671
|
+
200: jsonResponse$2(named("StudioPrompt", studioPromptSchema), "The prompt, with the resolved text."),
|
|
74174
74672
|
400: errorResponse(PROMPT_OWNER_OR_VERSION_REFUSAL),
|
|
74175
74673
|
403: forbiddenResponse("config:write"),
|
|
74176
74674
|
404: errorResponse(PROMPT_NOT_FOUND_REFUSAL)
|
|
@@ -74208,7 +74706,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74208
74706
|
body: jsonBody(named("UpdateStudioPromptRequest", updateStudioPromptRequestSchema), "The fields to change. At least one of `content`, `name`, `description` or `metadata`.")
|
|
74209
74707
|
},
|
|
74210
74708
|
responses: {
|
|
74211
|
-
200: jsonResponse$
|
|
74709
|
+
200: jsonResponse$2(named("StudioPrompt", studioPromptSchema), "The prompt as it now stands — still publishing whatever it published before."),
|
|
74212
74710
|
400: errorResponse(`The body failed its schema (\`invalid_payload\`), or ${PROMPT_OWNER_REFUSAL}`),
|
|
74213
74711
|
403: forbiddenResponse("config:write"),
|
|
74214
74712
|
404: errorResponse(PROMPT_NOT_FOUND_REFUSAL)
|
|
@@ -74259,7 +74757,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74259
74757
|
description: "A SOFT delete — the rows are kept for the audit window and every read filters them out. Refused while the prompt still publishes something: deleting text an owner is reading takes it away mid-flight, and the operator who typed the delete is rarely the one who finds out. Unpublish first.",
|
|
74260
74758
|
request: { params: loosePromptPath },
|
|
74261
74759
|
responses: {
|
|
74262
|
-
200: jsonResponse$
|
|
74760
|
+
200: jsonResponse$2(named("StudioPromptDeleteResponse", studioPromptDeleteResponseSchema), "The prompt that was removed."),
|
|
74263
74761
|
400: errorResponse(PROMPT_OWNER_REFUSAL),
|
|
74264
74762
|
403: forbiddenResponse("config:write"),
|
|
74265
74763
|
404: errorResponse(PROMPT_NOT_FOUND_REFUSAL),
|
|
@@ -74301,7 +74799,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74301
74799
|
description: "Every version written under this key, newest first, each flagged with whether it is the one that RUNS. The history is the only place an operator can see which of several drafts is live. Capped at 200.",
|
|
74302
74800
|
request: { params: loosePromptPath },
|
|
74303
74801
|
responses: {
|
|
74304
|
-
200: jsonResponse$
|
|
74802
|
+
200: jsonResponse$2(named("StudioPromptVersionListResponse", studioPromptVersionListResponseSchema), "The versions written under this key, newest first."),
|
|
74305
74803
|
400: errorResponse(PROMPT_OWNER_REFUSAL),
|
|
74306
74804
|
403: forbiddenResponse("config:write"),
|
|
74307
74805
|
404: errorResponse(PROMPT_NOT_FOUND_REFUSAL)
|
|
@@ -74326,7 +74824,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74326
74824
|
description: "A HARD delete, unlike the prompt's own: a draft nobody published is an attempt, not a record. The PUBLISHED version is refused outright — it is what the owner runs. `latestVersion` is recomputed afterwards, so the head never points at a number the history no longer holds.",
|
|
74327
74825
|
request: { params: loosePromptVersionPath },
|
|
74328
74826
|
responses: {
|
|
74329
|
-
200: jsonResponse$
|
|
74827
|
+
200: jsonResponse$2(named("StudioPromptVersionDeleteResponse", studioPromptVersionDeleteResponseSchema), "The version that was removed."),
|
|
74330
74828
|
400: errorResponse(PROMPT_OWNER_OR_VERSION_REFUSAL),
|
|
74331
74829
|
403: forbiddenResponse("config:write"),
|
|
74332
74830
|
404: errorResponse(PROMPT_NOT_FOUND_REFUSAL),
|
|
@@ -74388,7 +74886,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74388
74886
|
body: jsonBody(named("StudioPromptPublishRequest", studioPromptPublishRequestSchema), "The version to make live.")
|
|
74389
74887
|
},
|
|
74390
74888
|
responses: {
|
|
74391
|
-
200: jsonResponse$
|
|
74889
|
+
200: jsonResponse$2(named("StudioPrompt", studioPromptSchema), "The prompt, now publishing the version that was asked for."),
|
|
74392
74890
|
400: errorResponse(`The body failed its schema (\`invalid_payload\`), or ${PROMPT_OWNER_REFUSAL}`),
|
|
74393
74891
|
403: forbiddenResponse("config:write"),
|
|
74394
74892
|
404: errorResponse(`${PROMPT_NOT_FOUND_REFUSAL} A version that was never written answers \`studio_prompt_version_not_found\`.`)
|
|
@@ -74435,7 +74933,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74435
74933
|
description: "Clears the published pointer. The versions stay — what goes is what RUNS, which is exactly what the two delete refusals on this surface are waiting on. An `agent` owner is rebuilt afterwards: taking text out of service changes what it answers just as much as putting text in.",
|
|
74436
74934
|
request: { params: loosePromptPath },
|
|
74437
74935
|
responses: {
|
|
74438
|
-
200: jsonResponse$
|
|
74936
|
+
200: jsonResponse$2(named("StudioPrompt", studioPromptSchema), "The prompt, now publishing nothing."),
|
|
74439
74937
|
400: errorResponse(PROMPT_OWNER_REFUSAL),
|
|
74440
74938
|
403: forbiddenResponse("config:write"),
|
|
74441
74939
|
404: errorResponse(PROMPT_NOT_FOUND_REFUSAL)
|
|
@@ -74480,7 +74978,7 @@ var createPromptsRouter = (opts) => {
|
|
|
74480
74978
|
body: jsonBody(named("StudioPromptCompileRequest", studioPromptCompileRequestSchema), "The variables to render with, and optionally the version — or the unsaved draft — to render.")
|
|
74481
74979
|
},
|
|
74482
74980
|
responses: {
|
|
74483
|
-
200: jsonResponse$
|
|
74981
|
+
200: jsonResponse$2(named("StudioPromptCompileResponse", studioPromptCompileResponseSchema), "The rendered text, or the diagnostic saying why there is none."),
|
|
74484
74982
|
400: errorResponse(`The body failed its schema (\`invalid_payload\`), or ${PROMPT_OWNER_REFUSAL}`),
|
|
74485
74983
|
403: forbiddenResponse("config:write"),
|
|
74486
74984
|
404: errorResponse(`${PROMPT_NOT_FOUND_REFUSAL} A pinned version that was never written answers \`studio_prompt_version_not_found\`.`)
|
|
@@ -74861,7 +75359,7 @@ var createConfigRouter = (agentConfigOptions) => {
|
|
|
74861
75359
|
summary: "Read the active agent config",
|
|
74862
75360
|
description: "The active `AGENT_CONFIG` value, the JSON Schema the creator declared for it, and which version is live. `value` is null until the agent is configured for the first time; `schema` is null when the workspace declares none.",
|
|
74863
75361
|
responses: {
|
|
74864
|
-
200: jsonResponse$
|
|
75362
|
+
200: jsonResponse$2(named("StudioAgentConfig", studioAgentConfigGetResponseSchema), "The active config, its schema and its version."),
|
|
74865
75363
|
403: forbiddenResponse("config:write")
|
|
74866
75364
|
}
|
|
74867
75365
|
}), async (c) => c.json(await getAgentConfig(agentConfigOptions), 200));
|
|
@@ -74874,7 +75372,7 @@ var createConfigRouter = (agentConfigOptions) => {
|
|
|
74874
75372
|
description: "Appends a version and makes it active. A value structurally identical to the active one is a no-op: the reply comes back `unchanged: true` and no version is written.",
|
|
74875
75373
|
request: { body: jsonBody(named("StudioAgentConfigPutRequest", studioAgentConfigPutRequestSchema), "The new `AGENT_CONFIG` value, as a JSON object.") },
|
|
74876
75374
|
responses: {
|
|
74877
|
-
200: jsonResponse$
|
|
75375
|
+
200: jsonResponse$2(named("StudioAgentConfigPutResponse", studioAgentConfigPutResponseSchema), "The new active config, the value it replaced, and whether anything changed."),
|
|
74878
75376
|
400: errorResponse("The body is not a `{ value: object }` (`invalid_payload`), or the value fails the JSON Schema the creator declared (`agent_config_invalid`)."),
|
|
74879
75377
|
403: forbiddenResponse("config:write")
|
|
74880
75378
|
}
|
|
@@ -74895,7 +75393,7 @@ var createConfigRouter = (agentConfigOptions) => {
|
|
|
74895
75393
|
summary: "List the config versions",
|
|
74896
75394
|
description: "The 100 most recent versions, newest first, each with its value — so the editor can diff or preview one without a second round-trip.",
|
|
74897
75395
|
responses: {
|
|
74898
|
-
200: jsonResponse$
|
|
75396
|
+
200: jsonResponse$2(named("StudioAgentConfigVersionListResponse", studioAgentConfigVersionListResponseSchema), "The version history, newest first."),
|
|
74899
75397
|
403: forbiddenResponse("config:write")
|
|
74900
75398
|
}
|
|
74901
75399
|
}), async (c) => c.json(await listAgentConfigVersions(agentConfigOptions), 200));
|
|
@@ -74908,7 +75406,7 @@ var createConfigRouter = (agentConfigOptions) => {
|
|
|
74908
75406
|
description: "Re-applies an earlier version's value as a NEW version — the history is append-only, so a rollback is never a deletion.",
|
|
74909
75407
|
request: { params: looseVersionPath },
|
|
74910
75408
|
responses: {
|
|
74911
|
-
200: jsonResponse$
|
|
75409
|
+
200: jsonResponse$2(named("StudioAgentConfigPutResponse", studioAgentConfigPutResponseSchema), "The rolled-back value, now active as the newest version."),
|
|
74912
75410
|
403: forbiddenResponse("config:write"),
|
|
74913
75411
|
404: errorResponse("No such version — or a version that is not a number at all.")
|
|
74914
75412
|
}
|
|
@@ -76111,7 +76609,7 @@ var registerAuthorizeRoutes = (app, { getContext, registry }) => {
|
|
|
76111
76609
|
description: "Mints a token against the provider with the stored `client_credentials` and reports what happened. An app-only connector has no interactive \"connect account\" step, so this is the only way an operator finds out the credentials and tenant actually work.\n\nA provider REJECTION is a 200 with `ok: false` and the provider’s own sentence, not an error status: the fix is in the provider’s console, and a 4xx here would read as the box refusing the call. Only a connector that cannot be tested this way at all — any `authMode` other than `client_credentials` — is a 400.",
|
|
76112
76610
|
request: { params: connectorPath },
|
|
76113
76611
|
responses: {
|
|
76114
|
-
200: jsonResponse$
|
|
76612
|
+
200: jsonResponse$2(named("ConnectConnectorTestResponse", connectConnectorTestResponseSchema), "What the provider said. `ok: false` is a completed test with a negative answer."),
|
|
76115
76613
|
400: errorResponse("This connector uses `oauth` or `api_key` auth, which has no credentials to test this way (`connect_test_not_supported`); or the broker refused the arguments (`invalid_args`, `invalid_grant`)."),
|
|
76116
76614
|
403: forbiddenResponse(CONNECT_CAPABILITY),
|
|
76117
76615
|
...brokerRefusalResponses("The test")
|
|
@@ -76164,7 +76662,7 @@ var registerAuthorizeRoutes = (app, { getContext, registry }) => {
|
|
|
76164
76662
|
body: describedJsonBodyOf(app, "ConnectAuthorizeRequest", authorizeConnectorRequestSchema, "Who the authorization is on behalf of. Omit it entirely for the plain service-account flow.")
|
|
76165
76663
|
},
|
|
76166
76664
|
responses: {
|
|
76167
|
-
200: jsonResponse$
|
|
76665
|
+
200: jsonResponse$2(named("ConnectAuthorizeResponse", connectAuthorizeResponseSchema), "The consent challenge. Note its `expiresAt` is an ISO 8601 STRING, unlike every other `expiresAt` on this surface."),
|
|
76168
76666
|
400: errorResponse("The body failed its schema (`invalid_payload`), or the broker refused the request — an `app` principal cannot drive an interactive flow (`invalid_args`, `invalid_grant`)."),
|
|
76169
76667
|
403: forbiddenResponse(CONNECT_CAPABILITY),
|
|
76170
76668
|
...brokerRefusalResponses("Starting the authorization")
|
|
@@ -76405,7 +76903,7 @@ var registerConnectorRoutes = (app, { opts, getContext, registry, unavailable, c
|
|
|
76405
76903
|
description: "Stores the operator’s credentials for one connector, keyed on `connectorId` — posting the same id again UPSERTS the registration in place rather than failing. `clientSecret` is write-only: it is encrypted at rest by the box cipher and the readback carries only `hasClientSecret`.\n\nWhich fields are required depends on `authMode`, and the rule is enforced but not expressible in this schema: `api_key` needs only `clientSecret` (the key itself); `client_credentials` needs `clientId`, `clientSecret` and `tokenUrl`; `oauth` needs those three plus `authorizeUrl`. A body that misses one is refused with `invalid_payload` and the issue pinned to that field.\n\nA CUSTOM connector — one the workspace defines rather than picking from the catalog — additionally sends `baseUrl` and `specSource`; `protocol` is persisted on the row only for those.\n\nAn imported PIECE (`protocol: piece`) registers with `authMode: custom`, no `placement` and no `clientSecret`: it sends `credentialValues`, one value per `credentialFields` entry of its catalog definition. They are stored encrypted as the connection credential and never read back; a field that is not `secret` (a select with `options`, or a `type: number` field) is also kept as `extraFields` for the edit form. A field with `options` takes one of them, and a `number` field a number, sent as its text. A missing required field, a value off those, or an unknown key is refused on that field, naming it and never the value.\n\nA piece whose catalog entry lists more than one of `authMethods` (a GitHub App or a personal access token) also sends `authMethod`, the chosen `id`, and its values are checked against the fields of that method alone. The row keeps the method; re-registering under another one replaces it and the stored values together.\n\nA piece method of type `OAUTH2` registers with `authMode: oauth`, its `authMethod` and an OAuth app’s `clientId` and `clientSecret`: the row takes the method’s `authorizeUrl`, `tokenUrl` and `scopes` from the catalog definition, and a body that sends different ones is refused on that field. The grant comes from `POST /connectors/{connectorId}/authorize`. A method whose catalog entry lists `client_credentials` among its `grants` registers under `authMode: client_credentials` instead: no authorize step, the token is minted from the app itself.\n\nA method with `fields` (props such as a cloud, a tenant or a mailbox) also sends them as `credentialValues`; a blank one takes the field’s `defaultValue`, and a field with `options` takes one of them. Its URLs and scopes may name those fields as `{name}`: the row stores them filled in (URL-encoded in a URL, a scope value split on whitespace), and a name left without a value is refused on `credentialValues.<name>` with nothing written. The values are not secret; they are stored as `extraFields` and handed to the piece.\n\n`allowedOperations` has three states: absent keeps the stored fence (a key rotation posts the blank form), `null` removes it, an array sets it — and an array is checked against the armed triggers exactly as `PUT /connectors/{connectorId}/allowed-operations` checks it.",
|
|
76406
76904
|
request: { body: jsonBody(named("RegisterConnectorRequest", registerConnectorRequestSchema), "The registration. Mirrors the cloud studio-module DTO field for field.") },
|
|
76407
76905
|
responses: {
|
|
76408
|
-
200: jsonResponse$
|
|
76906
|
+
200: jsonResponse$2(CONNECT_CONNECTOR, "The registration as it now stands, read back through the masking projection."),
|
|
76409
76907
|
400: errorResponse("The body failed its schema, including the per-`authMode` required-field rules (`invalid_payload`).\n\nAlso `invalid_payload` when a `specSource` is sent for a connector this box already defines: a stored spec supersedes the catalog entry ENTIRELY, taking the delete guard, the provider error classes and the filter operators with it. Register a custom connector under an id of your own.\n\n`invalid_credential` when a piece’s own check refused the credential; `message` is the piece’s sentence. Nothing was saved."),
|
|
76410
76908
|
403: forbiddenResponse(CONNECT_CAPABILITY),
|
|
76411
76909
|
502: errorResponse("A piece could not be asked whether the credential works (`piece_runtime`): its bundle did not load, or its check did not answer in time. Nothing was saved; this is not a verdict on the credential."),
|
|
@@ -76510,7 +77008,7 @@ var registerConnectorRoutes = (app, { opts, getContext, registry, unavailable, c
|
|
|
76510
77008
|
summary: "List the connector gallery",
|
|
76511
77009
|
description: "Every brand this box knows how to connect: the built-in catalog, the Activepieces pieces the box lists, plus whatever the workspace declares under `<projectRoot>/connect/*.json` (a workspace document supersedes a built-in by reusing its id). Presentation and PRE-FILL only — `authorizeUrl`, `tokenUrl`, `scopes` and `placement` are what the register form fills in for the operator, and `requiredFields` is what they must still supply.\n\n`unavailable` lists the Activepieces pieces that never build, never load or carry a malformed manifest entry, each with its code, reason and logo. None of them is among `items`, and registering one is refused under any `authMode` (`invalid_payload`).\n\nThis is the catalog, not the registrations: `GET /api/connect/connectors` is what this box has actually registered.",
|
|
76512
77010
|
responses: {
|
|
76513
|
-
200: jsonResponse$
|
|
77011
|
+
200: jsonResponse$2(named("ConnectCatalogResponse", connectCatalogResponseSchema), "The gallery entries, in registry order."),
|
|
76514
77012
|
403: forbiddenResponse(CONNECT_CAPABILITY)
|
|
76515
77013
|
}
|
|
76516
77014
|
}), (c) => c.json({
|
|
@@ -76526,7 +77024,7 @@ var registerConnectorRoutes = (app, { opts, getContext, registry, unavailable, c
|
|
|
76526
77024
|
summary: "Retry loading the default connector catalog",
|
|
76527
77025
|
description: "Tries the default Activepieces catalog this box missed at boot right away, instead of waiting for the next background attempt. A call while an attempt is already running waits for that one rather than starting a second. When the attempt fails, the background retries start over from their first wait (30 s).\n\nAnswers once the attempt is over, with the same `catalogLoad` that `GET /api/connect/catalog` carries: `failed: false` means the catalog loaded and the gallery now lists its pieces.",
|
|
76528
77026
|
responses: {
|
|
76529
|
-
200: jsonResponse$
|
|
77027
|
+
200: jsonResponse$2(named("ConnectCatalogRetryResponse", connectCatalogRetryResponseSchema), "Where the box stands on the default catalog after the attempt."),
|
|
76530
77028
|
403: forbiddenResponse(CONNECT_CAPABILITY),
|
|
76531
77029
|
404: errorResponse("This box is not retrying a default catalog (`catalog_retry_not_found`): it loaded one at boot, names its own, or has none configured.")
|
|
76532
77030
|
}
|
|
@@ -76543,7 +77041,7 @@ var registerConnectorRoutes = (app, { opts, getContext, registry, unavailable, c
|
|
|
76543
77041
|
summary: "List the registered connectors",
|
|
76544
77042
|
description: "What this box has credentials for, ordered by provider. `hasClientSecret` is the entire readback of the stored secret — the ciphertext never leaves the box, and neither does any token.",
|
|
76545
77043
|
responses: {
|
|
76546
|
-
200: jsonResponse$
|
|
77044
|
+
200: jsonResponse$2(named("ConnectConnectorListResponse", connectConnectorListResponseSchema), "The registrations, each through the masking projection."),
|
|
76547
77045
|
403: forbiddenResponse(CONNECT_CAPABILITY)
|
|
76548
77046
|
}
|
|
76549
77047
|
}), async (c) => {
|
|
@@ -76572,7 +77070,7 @@ var registerConnectorRoutes = (app, { opts, getContext, registry, unavailable, c
|
|
|
76572
77070
|
description: "Drops the stored credentials AND disarms the inbound triggers that polled through this connection, answering how many went off with it (PLA-1095). Those were two acts that looked like one: a link left armed on a deleted connection failed every tick forever while the Triggers screen still read it as healthy. Disarming stops the polling and nothing else — the poll cursor stays put, so re-registering the connection and re-arming the link resumes from where it left off rather than from now.\n\nThe GRANTS made against this connector are NOT dropped with it — the grant row carries no foreign key to the registration, deliberately, so a re-registration under the same id keeps working. Revoke a grant separately with `DELETE /api/connect/grants/{grantId}`.",
|
|
76573
77071
|
request: { params: connectorPath },
|
|
76574
77072
|
responses: {
|
|
76575
|
-
200: jsonResponse$
|
|
77073
|
+
200: jsonResponse$2(named("ConnectConnectorDeleteResponse", connectConnectorDeleteResponseSchema), "The registration is gone."),
|
|
76576
77074
|
403: forbiddenResponse(CONNECT_CAPABILITY),
|
|
76577
77075
|
404: errorResponse("No registration under that id (`connector_installation_required`).")
|
|
76578
77076
|
}
|
|
@@ -76620,7 +77118,7 @@ var registerConnectorRoutes = (app, { opts, getContext, registry, unavailable, c
|
|
|
76620
77118
|
body: jsonBody(named("ConnectConnectorFenceRequest", connectConnectorFenceRequestSchema), "The fence, or `null` to remove it.")
|
|
76621
77119
|
},
|
|
76622
77120
|
responses: {
|
|
76623
|
-
200: jsonResponse$
|
|
77121
|
+
200: jsonResponse$2(CONNECT_CONNECTOR, "The registration as it now stands."),
|
|
76624
77122
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
76625
77123
|
403: forbiddenResponse(CONNECT_CAPABILITY),
|
|
76626
77124
|
404: errorResponse("No registration under that id (`connector_installation_required`)."),
|
|
@@ -76658,7 +77156,7 @@ var registerConnectorRoutes = (app, { opts, getContext, registry, unavailable, c
|
|
|
76658
77156
|
description: "Drops the cached spec document for this registration. The next action-picker open (and the next action run) downloads the provider's current spec instead of the one this box already holds — which is how a table added to a Supabase project since the last read shows up.\n\nNothing is downloaded here: the refresh evicts, the next read fetches. A connector whose spec is EMBEDDED has nothing cached and answers `not_cached`.",
|
|
76659
77157
|
request: { params: connectorPath },
|
|
76660
77158
|
responses: {
|
|
76661
|
-
200: jsonResponse$
|
|
77159
|
+
200: jsonResponse$2(named("ConnectConnectorSpecRefresh", connectConnectorSpecRefreshResponseSchema), "The cached spec is gone, or there was none to drop."),
|
|
76662
77160
|
403: forbiddenResponse(CONNECT_CAPABILITY),
|
|
76663
77161
|
404: errorResponse("No registration under that id (`connector_installation_required`).")
|
|
76664
77162
|
}
|
|
@@ -76699,7 +77197,7 @@ var registerConnectorRoutes = (app, { opts, getContext, registry, unavailable, c
|
|
|
76699
77197
|
description: "What the connector can be asked to do, for the action picker: `operations` for an OpenAPI/Swagger connector, `tools` for an MCP one. A CUSTOM connector is introspected from the spec on its own row; a built-in from the registry definition its registration references.\n\nA probe that FAILS is still a 200: the reason lands in `warnings` with an empty result, because \"this connector advertises nothing right now\" is a state the picker renders rather than an error it has to recover from. The same is true when the workspace has no definition to probe at all.",
|
|
76700
77198
|
request: { params: connectorPath },
|
|
76701
77199
|
responses: {
|
|
76702
|
-
200: jsonResponse$
|
|
77200
|
+
200: jsonResponse$2(named("ConnectConnectorOperations", introspectionResultSchema), "The operations or tools discovered, plus any non-fatal notes from the probe."),
|
|
76703
77201
|
403: forbiddenResponse(CONNECT_CAPABILITY),
|
|
76704
77202
|
404: errorResponse("No registration under that id (`connector_installation_required`). A registration with nothing to introspect is a 200 with warnings, not this.")
|
|
76705
77203
|
}
|
|
@@ -76798,7 +77296,7 @@ var registerGrantRoutes = (app, { getContext }) => {
|
|
|
76798
77296
|
summary: "List the stored authorizations",
|
|
76799
77297
|
description: "Every grant this box holds, ordered by connector then principal. METADATA ONLY: the row behind each entry holds an encrypted access token and, for an interactive grant, an encrypted refresh token, and neither is reachable from any management route. A credential leaves this box only as a short-lived mint on `POST /api/connect/token`.",
|
|
76800
77298
|
responses: {
|
|
76801
|
-
200: jsonResponse$
|
|
77299
|
+
200: jsonResponse$2(named("ConnectGrantListResponse", connectGrantListResponseSchema), "The grants, with health and expiry but no token material."),
|
|
76802
77300
|
403: forbiddenResponse(CONNECT_CAPABILITY)
|
|
76803
77301
|
}
|
|
76804
77302
|
}), async (c) => {
|
|
@@ -76819,7 +77317,7 @@ var registerGrantRoutes = (app, { getContext }) => {
|
|
|
76819
77317
|
description: "Drops the durable authorization AND the token cached from it, so nothing minted before this call survives. The principal has to authorize from scratch next time — which is what separates this from `POST /api/connect/evict`, the agent wire’s targeted cache drop that leaves the grant standing.",
|
|
76820
77318
|
request: { params: grantPath },
|
|
76821
77319
|
responses: {
|
|
76822
|
-
200: jsonResponse$
|
|
77320
|
+
200: jsonResponse$2(named("ConnectGrantRevokeResponse", connectGrantRevokeResponseSchema), "The grant and its cached token are gone."),
|
|
76823
77321
|
403: forbiddenResponse(CONNECT_CAPABILITY),
|
|
76824
77322
|
404: errorResponse("No grant with that row id on this box (`not_found`).")
|
|
76825
77323
|
}
|
|
@@ -76920,7 +77418,7 @@ var init_support = __esmMin((() => {
|
|
|
76920
77418
|
AGENT_WIRE_TAG = AGENT_CONNECT_TAG;
|
|
76921
77419
|
AGENT_WIRE_401 = errorResponse("The scheme-A signature is missing, malformed, computed with the wrong secret or outside the 5-minute drift window (`access_denied`) — or the signature was fine and the BROKER has no authorization to work with (`no_token`, `user_authorization_required`, `principal_required`).");
|
|
76922
77420
|
CONNECT_EXECUTE_FAILURE = named("ConnectExecuteFailure", connectExecuteFailureSchema);
|
|
76923
|
-
executeRefusal = (description) => jsonResponse$
|
|
77421
|
+
executeRefusal = (description) => jsonResponse$2(z.union([CONNECT_EXECUTE_FAILURE, errorEnvelopeSchema]), description);
|
|
76924
77422
|
answerExecuteFailure = (c, error) => {
|
|
76925
77423
|
const body = {
|
|
76926
77424
|
ok: false,
|
|
@@ -77039,7 +77537,7 @@ var createConnectTokenRouter = (opts, sharedContext) => {
|
|
|
77039
77537
|
description: "The runtime hot path: a `@stackbone/sdk/connect` adapter calls this before a tool call. The response carries `placement` — every slot the credential occupies — so the caller applies it where the OPERATOR configured it rather than guessing. It is the same set `POST /execute` applies inside the box, which is what keeps the two paths from disagreeing about one connector.\n\nThe two consumers read it differently, and only one of them can. `connectHeaders()` builds the request headers itself, so it honours the set: a `header` placement writes the raw token to that header, a `bearer` one writes `Authorization: Bearer`. `connect()` / `withConnect()` cannot — a connection runtime carries only a bearer through `auth.getToken`, and `TokenResult` has no slot for a placement — so that path stays BEARER-ONLY by construction. A non-bearer connector wired through it fails at the provider, not here: a key the provider reads from a header of its own, such as the `apikey` header the built-in `supabase` places beside its bearer, never reaches that header. Use `connectHeaders()` or `POST /execute` for those.\n\nThe signature proves POSSESSION of the workspace secret, not WHICH install is calling, so the body asserts its own scoping claims: `installationId` and `agentName` are read as claims, `connector` and `principal` pick the grant. A connector with no grant answers `no_token` on 401 and the adapter turns that into an authorization prompt.",
|
|
77040
77538
|
request: { body: jsonBody(named("ConnectTokenRequest", tokenRequestSchema), "Which connector, for which principal, on behalf of which install.") },
|
|
77041
77539
|
responses: {
|
|
77042
|
-
200: jsonResponse$
|
|
77540
|
+
200: jsonResponse$2(named("ConnectTokenResponse", connectTokenResponseSchema), "The minted credential. `expiresAt` is absent when the provider declared no expiry."),
|
|
77043
77541
|
400: errorResponse("The body failed its schema (`invalid_payload`), or the broker refused the request (`invalid_args`, `invalid_grant`)."),
|
|
77044
77542
|
401: AGENT_WIRE_401,
|
|
77045
77543
|
404: errorResponse("The connector is not registered on this box (`connector_installation_required`, `not_found`)."),
|
|
@@ -77079,7 +77577,7 @@ var createConnectTokenRouter = (opts, sharedContext) => {
|
|
|
77079
77577
|
description: "Mints the credential, applies it in the connector’s declared placement, calls the operation and hands back ONLY its output. Nothing about the credential — not the token, not its placement — crosses this boundary, which is the whole reason a workflow step calls the box instead of the provider.\n\n`operation` names an operation id (OpenAPI) or a tool (MCP) as `GET /api/connect/connectors/{connectorId}/operations` reports it. `runId` is the one field that changes WHICH connector answers: a run wired to a stand-in reaches the stand-in and never leaves the box, which is how an eval exercises a send without sending anything.\n\nA refusal comes back in TWO shapes on the same statuses: `{ ok: false, error }` when the executor completed and the provider or the broker said no, including a broker error thrown before execution completes. Signature and payload validation failures use the box’s `{ code, message }` envelope.",
|
|
77080
77578
|
request: { body: jsonBody(named("ConnectExecuteRequest", executeRequestSchema), "The connector, the operation, and the args to run it with.") },
|
|
77081
77579
|
responses: {
|
|
77082
|
-
200: jsonResponse$
|
|
77580
|
+
200: jsonResponse$2(named("ConnectExecuteSuccess", connectExecuteSuccessSchema), "The operation’s own output, passed through untouched."),
|
|
77083
77581
|
400: executeRefusal("The body failed its schema (`invalid_payload`), or the broker refused the arguments (`invalid_args`, `invalid_grant`)."),
|
|
77084
77582
|
401: executeRefusal("The signature is missing or wrong (`access_denied`), or the connector has no usable authorization (`no_token`, `user_authorization_required`, `principal_required`)."),
|
|
77085
77583
|
404: executeRefusal("No such connector on this box, or no such operation on it (`not_found`, `connector_installation_required`)."),
|
|
@@ -77122,7 +77620,7 @@ var createConnectTokenRouter = (opts, sharedContext) => {
|
|
|
77122
77620
|
description: "The adapter’s 401-recovery hook: a provider rejected a token that has not expired yet, so the cached copy is dropped and the NEXT mint fetches a fresh one. The durable GRANT survives — that is what separates this from `DELETE /api/connect/grants/{grantId}`, which drops the authorization and makes the principal start over.\n\nEvicting a connector with nothing cached is not an error; it answers the same `evicted` either way.",
|
|
77123
77621
|
request: { body: jsonBody(named("ConnectEvictRequest", evictRequestSchema), "Which connector, for which principal.") },
|
|
77124
77622
|
responses: {
|
|
77125
|
-
200: jsonResponse$
|
|
77623
|
+
200: jsonResponse$2(named("ConnectEvictResponse", connectEvictResponseSchema), "The cached credential for that pair is gone. The grant is untouched."),
|
|
77126
77624
|
400: errorResponse("The body failed its schema (`invalid_payload`), or the broker refused the arguments (`invalid_args`, `invalid_grant`)."),
|
|
77127
77625
|
401: AGENT_WIRE_401,
|
|
77128
77626
|
404: errorResponse("The connector is not registered on this box (`connector_installation_required`, `not_found`)."),
|
|
@@ -77343,7 +77841,7 @@ var createDbExplorerRouter = (deps) => {
|
|
|
77343
77841
|
summary: "List the schemas and their tables",
|
|
77344
77842
|
description: "The explorer sidebar: every schema `stackbone_viewer` can see, each with its tables and an ESTIMATED row count off `pg_class.reltuples` (never a `COUNT(*)`, which would scan the table to paint a sidebar). `stackbone_platform` sorts first.",
|
|
77345
77843
|
responses: {
|
|
77346
|
-
200: jsonResponse$
|
|
77844
|
+
200: jsonResponse$2(named("DbSchemasResponse", dbSchemasResponseSchema), "Every readable schema and its tables."),
|
|
77347
77845
|
403: forbiddenResponse("db:query")
|
|
77348
77846
|
}
|
|
77349
77847
|
}), async (c) => c.json(await listSchemas(dbExplorerOptions), 200));
|
|
@@ -77359,7 +77857,7 @@ var createDbExplorerRouter = (deps) => {
|
|
|
77359
77857
|
query: dbBrowseQuerySchema
|
|
77360
77858
|
},
|
|
77361
77859
|
responses: {
|
|
77362
|
-
200: jsonResponse$
|
|
77860
|
+
200: jsonResponse$2(named("DbBrowseResponse", dbBrowseResponseSchema), "One page of rows, and the cursor for the next."),
|
|
77363
77861
|
400: errorResponse("A segment is not a Postgres identifier, or the page controls failed their schema (`invalid_payload`) — or the cursor is not one this box issued (`invalid_cursor`)."),
|
|
77364
77862
|
403: forbiddenResponse("db:query"),
|
|
77365
77863
|
404: errorResponse("No such table in that schema (`table_not_found`).")
|
|
@@ -77392,7 +77890,7 @@ var createDbExplorerRouter = (deps) => {
|
|
|
77392
77890
|
description: `The ad-hoc editor. SELECT-only, one statement, run under \`stackbone_viewer\` with \`statement_timeout\` at ${VIEWER_STATEMENT_TIMEOUT_S}s; a query with no LIMIT (or a bigger one) is capped at ${STUDIO_DB_QUERY_ROW_LIMIT} rows and comes back \`truncated: true\`.`,
|
|
77393
77891
|
request: { body: jsonBody(named("DbQueryRequest", dbQueryRequestSchema), "The SQL to run.") },
|
|
77394
77892
|
responses: {
|
|
77395
|
-
200: jsonResponse$
|
|
77893
|
+
200: jsonResponse$2(named("DbQueryResponse", dbQueryResponseSchema), "The rows, their columns, whether the cap was applied, and how long it took."),
|
|
77396
77894
|
400: errorResponse("The body failed its schema (`invalid_payload`), or the SQL is one this surface refuses to run: `non_select`, `multi_statement`, `select_for_update`, `cte_write`, `invalid_sql`."),
|
|
77397
77895
|
403: forbiddenResponse("db:query")
|
|
77398
77896
|
}
|
|
@@ -77547,7 +78045,7 @@ var createAgentsRouter = (deps) => {
|
|
|
77547
78045
|
tags: [AGENTS_TAG],
|
|
77548
78046
|
summary: "List the in-process deep agents",
|
|
77549
78047
|
description: "Read straight off the registry, never probed over HTTP — a deep agent runs in this process, so `healthy` is its readiness and not a reachability check. A DEGRADED agent is listed, honestly: it is registered and named, its graph never built, and `reason` says whether that is a missing model provider or a bug in the creator's code. A workspace with no deep agents answers an empty list.",
|
|
77550
|
-
responses: { 200: jsonResponse$
|
|
78048
|
+
responses: { 200: jsonResponse$2(named("CatalogAgentListResponse", catalogAgentListResponseSchema), "Every deep agent this workspace registers.") }
|
|
77551
78049
|
}), (c) => {
|
|
77552
78050
|
const items = (deps.getDeepAgentRegistry()?.list() ?? []).map(projectDeepAgentSummary);
|
|
77553
78051
|
return c.json({ items }, 200);
|
|
@@ -77560,7 +78058,7 @@ var createAgentsRouter = (deps) => {
|
|
|
77560
78058
|
description: "The full inspection view: the model, the system prompt, the tool names and whether the agent can hold durable sessions. Tools are NAME-ONLY — a deep agent exposes no per-tool JSON Schema over the catalog — and there are no channels, skills, schedules or hooks.",
|
|
77561
78059
|
request: { params: agentNamePath },
|
|
77562
78060
|
responses: {
|
|
77563
|
-
200: jsonResponse$
|
|
78061
|
+
200: jsonResponse$2(named("CatalogAgentDetail", catalogAgentDetailSchema), "The agent, as the inspection screen reads it."),
|
|
77564
78062
|
404: errorResponse("No agent by that name (`agent_not_found`). The names this workspace DOES register ride back under `details.known`, so a typo is one round-trip to fix.")
|
|
77565
78063
|
}
|
|
77566
78064
|
}), async (c) => {
|
|
@@ -81034,7 +81532,7 @@ var createEvalsRouter = (options = {}) => {
|
|
|
81034
81532
|
tags: [EVALS_TAG],
|
|
81035
81533
|
summary: "List the criteria this build can judge with",
|
|
81036
81534
|
description: "Every criterion a suite may be written against, each carrying what it can judge (`targets`), a plain-language description and the JSON Schema of its own settings — a screen renders the criterion form straight from this. Two of those settings are painted with facts only this box holds and are therefore asked PER REQUEST: the rubric criterion's judging model (the chat models the configured provider lists, plus the one this workspace judges with) and the customer criterion's judging workflow (the workflows this box could start and await). Nothing is ever withheld: a box that can reach neither serves the same entries with EMPTY pickers. `simulatorWorkflow` rides beside the entries because which workflow plays the user is a SUITE setting, not a criterion one.",
|
|
81037
|
-
responses: { 200: jsonResponse$
|
|
81535
|
+
responses: { 200: jsonResponse$2(named("EvalScorerCatalogResponse", evalScorerCatalogResponseSchema), "One entry per criterion, plus the suite-level simulator picker.") }
|
|
81038
81536
|
}), async (c) => c.json(evalScorerCatalogResponseSchema.parse({
|
|
81039
81537
|
entries: await servedCatalog(options),
|
|
81040
81538
|
simulatorWorkflow: await servedSimulatorWorkflow(options)
|
|
@@ -81155,7 +81653,7 @@ var createEvalDatasetsRouter = (opts) => {
|
|
|
81155
81653
|
tags: [EVALS_TAG],
|
|
81156
81654
|
summary: "List the case lists",
|
|
81157
81655
|
description: "Every active list, WITHOUT its cases — the index screen paints a row from `caseCount` and `currentVersion`, and a thousand-case list per row would make the read useless. Read one list to get its cases.",
|
|
81158
|
-
responses: { 200: jsonResponse$
|
|
81656
|
+
responses: { 200: jsonResponse$2(named("EvalDatasetListResponse", evalDatasetListResponseSchema), "Every active list, cases omitted.") }
|
|
81159
81657
|
}), async (c) => c.json({ items: await opts.store.list() }, 200));
|
|
81160
81658
|
app.openapi(createRoute({
|
|
81161
81659
|
method: "post",
|
|
@@ -81166,7 +81664,7 @@ var createEvalDatasetsRouter = (opts) => {
|
|
|
81166
81664
|
description: "Writes a list at version 1. The cases ride in the body as ordinary JSON rows — there is no file upload anywhere on this surface — and an EMPTY `cases` is a real request: a list an operator will capture into from run details later.",
|
|
81167
81665
|
request: { body: jsonBody(named("CreateEvalDatasetRequest", createEvalDatasetRequestSchema), "The key, the name, and the case rows version 1 holds. Each case carries the `input` the target is given and, optionally, the `expected` answer, a `persona` to answer it back and the tags a suite filters on.") },
|
|
81168
81666
|
responses: {
|
|
81169
|
-
201: jsonResponse$
|
|
81667
|
+
201: jsonResponse$2(named("EvalDataset", evalDatasetSchema), "The stored list at version 1, cases included."),
|
|
81170
81668
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
81171
81669
|
403: forbiddenResponse("config:write"),
|
|
81172
81670
|
409: errorResponse("Another list already holds that key (`eval_dataset_key_taken`). Deleting the old one frees the name.")
|
|
@@ -81206,7 +81704,7 @@ var createEvalDatasetsRouter = (opts) => {
|
|
|
81206
81704
|
body: jsonBody(named("CaptureEvalCaseRequest", captureEvalCaseRequestSchema), "The case to append, plus the free-text `note` kept on the version this mints.")
|
|
81207
81705
|
},
|
|
81208
81706
|
responses: {
|
|
81209
|
-
201: jsonResponse$
|
|
81707
|
+
201: jsonResponse$2(named("EvalDataset", evalDatasetSchema), "The list at its NEW version, with the captured case on it."),
|
|
81210
81708
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
81211
81709
|
403: forbiddenResponse("config:write"),
|
|
81212
81710
|
404: errorResponse(LIST_NOT_FOUND_DESCRIPTION),
|
|
@@ -81248,7 +81746,7 @@ var createEvalDatasetsRouter = (opts) => {
|
|
|
81248
81746
|
query: datasetReadQuery
|
|
81249
81747
|
},
|
|
81250
81748
|
responses: {
|
|
81251
|
-
200: jsonResponse$
|
|
81749
|
+
200: jsonResponse$2(named("EvalDataset", evalDatasetSchema), "The list, with the cases of the version read."),
|
|
81252
81750
|
400: errorResponse("The query string failed its schema (`invalid_payload`) — a `version` that is not a positive integer, or a `tags` filter that names no tag."),
|
|
81253
81751
|
404: errorResponse(LIST_NOT_FOUND_DESCRIPTION)
|
|
81254
81752
|
}
|
|
@@ -81276,7 +81774,7 @@ var createEvalDatasetsRouter = (opts) => {
|
|
|
81276
81774
|
body: jsonBody(named("UpdateEvalDatasetRequest", updateEvalDatasetRequestSchema), "The fields to change. Everything is optional, but at least one of `cases`, `name` or `description` is required.")
|
|
81277
81775
|
},
|
|
81278
81776
|
responses: {
|
|
81279
|
-
200: jsonResponse$
|
|
81777
|
+
200: jsonResponse$2(named("EvalDataset", evalDatasetSchema), "The list as it now stands, at the version this edit left it on."),
|
|
81280
81778
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
81281
81779
|
403: forbiddenResponse("config:write"),
|
|
81282
81780
|
404: errorResponse(LIST_NOT_FOUND_DESCRIPTION),
|
|
@@ -81319,7 +81817,7 @@ var createEvalDatasetsRouter = (opts) => {
|
|
|
81319
81817
|
description: "A SOFT delete, so the runs that measured against this list keep their subject. The key is freed for reuse. No body — a delete addressed by its path takes nothing.",
|
|
81320
81818
|
request: { params: looseKeyPath },
|
|
81321
81819
|
responses: {
|
|
81322
|
-
200: jsonResponse$
|
|
81820
|
+
200: jsonResponse$2(named("EvalDatasetDeleteResponse", evalDatasetDeleteResponseSchema), "The key that was deleted, so a screen can drop the row it was showing without remembering which delete just landed."),
|
|
81323
81821
|
403: forbiddenResponse("config:write"),
|
|
81324
81822
|
404: errorResponse(LIST_NOT_FOUND_DESCRIPTION)
|
|
81325
81823
|
}
|
|
@@ -81394,7 +81892,7 @@ var createEvalRunsRouter = (opts) => {
|
|
|
81394
81892
|
body: describedJsonBodyOf(app, "LaunchEvalRunRequest", launchEvalRunRequestSchema, "What this launch seals about the thing it is measuring — the commit and the agent build nobody else can know — and, optionally, the variant columns to measure side by side. Every field is optional; `{}` is a complete launch.")
|
|
81395
81893
|
},
|
|
81396
81894
|
responses: {
|
|
81397
|
-
202: jsonResponse$
|
|
81895
|
+
202: jsonResponse$2(named("EvalRun", evalRunSchema), "The run row, durable and queued. NOTHING has been measured yet."),
|
|
81398
81896
|
400: errorResponse("The body failed its schema (`invalid_payload`), or the suite cannot be measured here — `eval_dataset_empty`, `eval_dataset_version_missing`, `eval_rubric_missing`, `eval_rubric_version_missing`, `eval_rubric_ambiguous`, `eval_target_unsupported` or `eval_variant_unsupported`. Each is refused before a case is measured."),
|
|
81399
81897
|
403: forbiddenResponse("evals:run"),
|
|
81400
81898
|
404: errorResponse("No suite with that id on this box (`eval_suite_not_found`).")
|
|
@@ -81457,7 +81955,7 @@ var createEvalRunsRouter = (opts) => {
|
|
|
81457
81955
|
summary: "Read a suite's run history",
|
|
81458
81956
|
description: "Every run of that suite, newest first, WITHOUT the per-case verdicts — the history screen paints a row from the totals, and a run with a thousand case results per row would make the read useless. Read one run to get its verdicts.",
|
|
81459
81957
|
request: { params: looseSuiteIdPath$1 },
|
|
81460
|
-
responses: { 200: jsonResponse$
|
|
81958
|
+
responses: { 200: jsonResponse$2(named("EvalRunListResponse", evalRunListResponseSchema), "That suite's runs, newest first. A suite this box does not hold has no runs, which is an empty list rather than a 404.") }
|
|
81461
81959
|
}), async (c) => c.json({ items: await opts.store.listForSuite(c.req.param("suiteId")) }, 200));
|
|
81462
81960
|
app.openapi(createRoute({
|
|
81463
81961
|
method: "get",
|
|
@@ -81467,7 +81965,7 @@ var createEvalRunsRouter = (opts) => {
|
|
|
81467
81965
|
description: "The most detailed answer on this surface: the run row, everything it SEALED about what it measured (the commit, the agent build, the model that answered and the model that graded), and one result per case with the score each criterion gave it.",
|
|
81468
81966
|
request: { params: looseRunIdPath },
|
|
81469
81967
|
responses: {
|
|
81470
|
-
200: jsonResponse$
|
|
81968
|
+
200: jsonResponse$2(named("EvalRunDetail", evalRunDetailSchema), "The run with its per-case verdicts."),
|
|
81471
81969
|
404: errorResponse(RUN_NOT_FOUND_DESCRIPTION)
|
|
81472
81970
|
}
|
|
81473
81971
|
}), async (c) => {
|
|
@@ -81483,7 +81981,7 @@ var createEvalRunsRouter = (opts) => {
|
|
|
81483
81981
|
description: "A suite's FIRST run does not get a row of zeros: `comparison` is `null` and `reason` says why in words, because \"we have never measured this before\" and \"nothing changed\" are opposite pieces of news. When there is a comparison it names the run it was measured against, so the deltas have a subject.",
|
|
81484
81982
|
request: { params: looseRunIdPath },
|
|
81485
81983
|
responses: {
|
|
81486
|
-
200: jsonResponse$
|
|
81984
|
+
200: jsonResponse$2(named("EvalRunComparisonResponse", evalRunComparisonResponseSchema), "The deltas and the run they are against — or `comparison: null` with the reason."),
|
|
81487
81985
|
404: errorResponse(RUN_NOT_FOUND_DESCRIPTION)
|
|
81488
81986
|
}
|
|
81489
81987
|
}), async (c) => {
|
|
@@ -81518,7 +82016,7 @@ var createEvalRunsRouter = (opts) => {
|
|
|
81518
82016
|
description: "The reporting read: one row per agent or workflow that has ever been measured here, without opening a suite. `system=exclude` leaves out the targets the platform installed for itself, as the suites list does. A literal segment beside `/runs/{runId}`, and registered before it, so it can never be read as a run id.",
|
|
81519
82017
|
request: { query: evalSystemEntriesQuerySchema },
|
|
81520
82018
|
responses: {
|
|
81521
|
-
200: jsonResponse$
|
|
82019
|
+
200: jsonResponse$2(named("EvalTargetSummaryResponse", evalTargetSummaryResponseSchema), "One row per measured target."),
|
|
81522
82020
|
400: errorResponse("The query failed its schema (`invalid_payload`).")
|
|
81523
82021
|
}
|
|
81524
82022
|
}), async (c) => c.json({ items: await opts.store.summaryByTarget(c.req.valid("query")) }, 200));
|
|
@@ -81532,7 +82030,7 @@ var createEvalRunsRouter = (opts) => {
|
|
|
81532
82030
|
description: "The engine reads this row between cases and stops there, SEALING what it already measured. Nothing here interrupts a case in flight — the platform has no live run cancellation, and pretending otherwise would leave a half-measured case looking like a real verdict. No body: the run is named by its path.",
|
|
81533
82031
|
request: { params: looseRunIdPath },
|
|
81534
82032
|
responses: {
|
|
81535
|
-
200: jsonResponse$
|
|
82033
|
+
200: jsonResponse$2(named("EvalRun", evalRunSchema), "The run row, now cancelled."),
|
|
81536
82034
|
403: forbiddenResponse("config:write"),
|
|
81537
82035
|
404: errorResponse(RUN_NOT_FOUND_DESCRIPTION)
|
|
81538
82036
|
}
|
|
@@ -81733,7 +82231,7 @@ var createEvalSuitesRouter = (opts) => {
|
|
|
81733
82231
|
description: "Every active suite, with the criteria and the settings each one measures under. `system=exclude` leaves out the suites that measure an entry the platform installed for itself.",
|
|
81734
82232
|
request: { query: evalSystemEntriesQuerySchema },
|
|
81735
82233
|
responses: {
|
|
81736
|
-
200: jsonResponse$
|
|
82234
|
+
200: jsonResponse$2(named("EvalSuiteListResponse", evalSuiteListResponseSchema), "Every active suite."),
|
|
81737
82235
|
400: errorResponse("The query failed its schema (`invalid_payload`).")
|
|
81738
82236
|
}
|
|
81739
82237
|
}), async (c) => c.json({ items: await opts.store.list(c.req.valid("query")) }, 200));
|
|
@@ -81746,7 +82244,7 @@ var createEvalSuitesRouter = (opts) => {
|
|
|
81746
82244
|
description: "Writes a suite: WHAT is measured (`targetKind` + `targetName`), over WHICH case list (`datasetId`, optionally pinned to a `datasetPin` version), with WHICH criteria, how many `repeats` and against what pass mark (`threshold`). Three validations run in this order and stop at the first — the wire schema, then the CATALOG (does this criterion exist, can it judge this target, does it understand these settings), then the target itself (is it served here). The catalog half is the very check `POST /api/evals/scorers/validate` runs, so a criterion the screen validated cannot be refused at save time.",
|
|
81747
82245
|
request: { body: jsonBody(named("CreateEvalSuiteRequest", createEvalSuiteRequestSchema), "The target, the case list, the criteria and the run settings.") },
|
|
81748
82246
|
responses: {
|
|
81749
|
-
201: jsonResponse$
|
|
82247
|
+
201: jsonResponse$2(named("EvalSuite", evalSuiteSchema), "The stored suite."),
|
|
81750
82248
|
400: errorResponse(SUITE_WRITE_REFUSAL),
|
|
81751
82249
|
403: forbiddenResponse("config:write"),
|
|
81752
82250
|
409: errorResponse("Another suite already holds that name (`eval_suite_name_taken`). Deleting the old one frees it.")
|
|
@@ -81799,7 +82297,7 @@ var createEvalSuitesRouter = (opts) => {
|
|
|
81799
82297
|
description: "The whole suite, criteria included.",
|
|
81800
82298
|
request: { params: looseSuiteIdPath },
|
|
81801
82299
|
responses: {
|
|
81802
|
-
200: jsonResponse$
|
|
82300
|
+
200: jsonResponse$2(named("EvalSuite", evalSuiteSchema), "The suite as it stands."),
|
|
81803
82301
|
404: errorResponse(SUITE_NOT_FOUND_DESCRIPTION)
|
|
81804
82302
|
}
|
|
81805
82303
|
}), async (c) => {
|
|
@@ -81819,7 +82317,7 @@ var createEvalSuitesRouter = (opts) => {
|
|
|
81819
82317
|
body: jsonBody(named("UpdateEvalSuiteRequest", updateEvalSuiteRequestSchema), "The fields to change. Everything is optional.")
|
|
81820
82318
|
},
|
|
81821
82319
|
responses: {
|
|
81822
|
-
200: jsonResponse$
|
|
82320
|
+
200: jsonResponse$2(named("EvalSuite", evalSuiteSchema), "The suite as it now stands."),
|
|
81823
82321
|
400: errorResponse(SUITE_WRITE_REFUSAL),
|
|
81824
82322
|
403: forbiddenResponse("config:write"),
|
|
81825
82323
|
404: errorResponse(SUITE_NOT_FOUND_DESCRIPTION),
|
|
@@ -81873,7 +82371,7 @@ var createEvalSuitesRouter = (opts) => {
|
|
|
81873
82371
|
description: "A SOFT delete, so the runs that measured under this suite keep their subject. The name is freed for reuse. No body — a delete addressed by its path takes nothing.",
|
|
81874
82372
|
request: { params: looseSuiteIdPath },
|
|
81875
82373
|
responses: {
|
|
81876
|
-
200: jsonResponse$
|
|
82374
|
+
200: jsonResponse$2(named("EvalSuiteDeleteResponse", evalSuiteDeleteResponseSchema), "The id that was deleted, so a screen can drop the row it was showing without remembering which delete just landed."),
|
|
81877
82375
|
403: forbiddenResponse("config:write"),
|
|
81878
82376
|
404: errorResponse(SUITE_NOT_FOUND_DESCRIPTION)
|
|
81879
82377
|
}
|
|
@@ -82444,7 +82942,7 @@ var createFixturesRouter = (deps) => {
|
|
|
82444
82942
|
tags: [FIXTURES_TAG],
|
|
82445
82943
|
summary: "List the saved Playground inputs",
|
|
82446
82944
|
description: "Every fixture this agent holds. The cursors are always null — the store is a per-agent JSONL file read whole — and stay on the wire so the shape matches the cloud list a Studio screen may also be reading.",
|
|
82447
|
-
responses: { 200: jsonResponse$
|
|
82945
|
+
responses: { 200: jsonResponse$2(named("FixtureListResponse", fixtureListResponseSchema), "Every saved fixture for this agent.") }
|
|
82448
82946
|
}), async (c) => c.json({
|
|
82449
82947
|
items: await fixturesStore.list(),
|
|
82450
82948
|
nextCursor: null,
|
|
@@ -82459,7 +82957,7 @@ var createFixturesRouter = (deps) => {
|
|
|
82459
82957
|
description: "Names an input so it can be replayed from the Playground. The name is unique per agent — saving over an existing one is refused rather than silently replacing the input somebody else pinned.",
|
|
82460
82958
|
request: { body: jsonBody(named("CreateFixtureRequest", createFixtureRequestSchema), "The name, the input to save under it, and an optional description.") },
|
|
82461
82959
|
responses: {
|
|
82462
|
-
201: jsonResponse$
|
|
82960
|
+
201: jsonResponse$2(named("Fixture", fixtureSchema), "The fixture as it was stored."),
|
|
82463
82961
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
82464
82962
|
403: forbiddenResponse("runs:invoke"),
|
|
82465
82963
|
409: errorResponse("This agent already holds a fixture by that name (`fixture_name_conflict`).")
|
|
@@ -83016,7 +83514,7 @@ var createGuardrailsRouter = (opts) => {
|
|
|
83016
83514
|
tags: [GUARDRAILS_TAG],
|
|
83017
83515
|
summary: "List the checks this build can run",
|
|
83018
83516
|
description: "Every check a rule may be written against, each carrying the sides of a turn it can guard (`targets`) and the JSON Schema of its own settings. A screen renders the rule form straight from this — the same description the runtime enforces a write against, so a form and the box cannot disagree about what a check accepts. It is a CONSTANT of the build: no store is read, and the answer only changes when the box's image does.",
|
|
83019
|
-
responses: { 200: jsonResponse$
|
|
83517
|
+
responses: { 200: jsonResponse$2(named("GuardrailCatalogResponse", guardrailCatalogResponseSchema), "One entry per check this build can run.") }
|
|
83020
83518
|
}), (c) => c.json(CATALOG_RESPONSE, 200));
|
|
83021
83519
|
app.openapi(createRoute({
|
|
83022
83520
|
method: "get",
|
|
@@ -83024,7 +83522,7 @@ var createGuardrailsRouter = (opts) => {
|
|
|
83024
83522
|
tags: [GUARDRAILS_TAG],
|
|
83025
83523
|
summary: "List the stored rules",
|
|
83026
83524
|
description: "Every guardrail this box holds, each with its attachments inline — so a screen can show what an edit or a delete would touch without a second request per row.",
|
|
83027
|
-
responses: { 200: jsonResponse$
|
|
83525
|
+
responses: { 200: jsonResponse$2(named("GuardrailsListResponse", guardrailsListResponseSchema), "Every stored rule, with its attachments.") }
|
|
83028
83526
|
}), async (c) => c.json({ items: await opts.store.list() }, 200));
|
|
83029
83527
|
app.openapi(createRoute({
|
|
83030
83528
|
method: "post",
|
|
@@ -83035,7 +83533,7 @@ var createGuardrailsRouter = (opts) => {
|
|
|
83035
83533
|
description: "Writes a new rule and, when the body carries an `attachment`, points it somewhere in the same breath — a rule and its blast radius are one fact, and splitting them would leave a window where a rule meant for one agent is live across the whole workspace. Absent means the workspace default set.",
|
|
83036
83534
|
request: { body: jsonBody(named("CreateGuardrailRequest", createGuardrailRequestSchema), "The check, its settings, what it does when it fires, and optionally where it applies.") },
|
|
83037
83535
|
responses: {
|
|
83038
|
-
201: jsonResponse$
|
|
83536
|
+
201: jsonResponse$2(named("Guardrail", guardrailSchema), "The stored rule, with the attachments it now carries."),
|
|
83039
83537
|
400: errorResponse("The body failed its schema (`invalid_payload`), or it read fine and the CHOSEN CHECK would not run under these settings — `details.errors` names which, one at a time."),
|
|
83040
83538
|
403: forbiddenResponse("config:write"),
|
|
83041
83539
|
409: errorResponse("Another rule already holds that name (`guardrail_name_taken`). Names are what a blocked user reads, so they are unique per box.")
|
|
@@ -83072,7 +83570,7 @@ var createGuardrailsRouter = (opts) => {
|
|
|
83072
83570
|
description: "The whole definition, attachments included.",
|
|
83073
83571
|
request: { params: looseGuardrailIdPath },
|
|
83074
83572
|
responses: {
|
|
83075
|
-
200: jsonResponse$
|
|
83573
|
+
200: jsonResponse$2(named("Guardrail", guardrailSchema), "The rule as it stands."),
|
|
83076
83574
|
404: errorResponse(RULE_NOT_FOUND_DESCRIPTION)
|
|
83077
83575
|
}
|
|
83078
83576
|
}), async (c) => {
|
|
@@ -83092,7 +83590,7 @@ var createGuardrailsRouter = (opts) => {
|
|
|
83092
83590
|
body: jsonBody(named("UpdateGuardrailRequest", updateGuardrailRequestSchema), "The fields to change. Everything is optional.")
|
|
83093
83591
|
},
|
|
83094
83592
|
responses: {
|
|
83095
|
-
200: jsonResponse$
|
|
83593
|
+
200: jsonResponse$2(named("Guardrail", guardrailSchema), "The rule as it now stands."),
|
|
83096
83594
|
400: errorResponse("The body failed its schema (`invalid_payload`), or the MERGED row — this patch over the stored values — is a rule its check would not run."),
|
|
83097
83595
|
403: forbiddenResponse("config:write"),
|
|
83098
83596
|
404: errorResponse(RULE_NOT_FOUND_DESCRIPTION),
|
|
@@ -83177,7 +83675,7 @@ var createGuardrailsRouter = (opts) => {
|
|
|
83177
83675
|
body: jsonBody(named("AttachGuardrailRequest", attachGuardrailRequestSchema), "The scope, plus the unit name when the scope is a single agent or workflow.")
|
|
83178
83676
|
},
|
|
83179
83677
|
responses: {
|
|
83180
|
-
201: jsonResponse$
|
|
83678
|
+
201: jsonResponse$2(named("Guardrail", guardrailSchema), "The WHOLE rule, not just the new row: the screen repaints the blast radius from this answer instead of re-reading the list."),
|
|
83181
83679
|
400: errorResponse("The body failed its schema (`invalid_payload`) — a per-unit scope with no `unitName`, or a scope this build does not know."),
|
|
83182
83680
|
403: forbiddenResponse("config:write"),
|
|
83183
83681
|
404: errorResponse(RULE_NOT_FOUND_DESCRIPTION)
|
|
@@ -85724,7 +86222,7 @@ var createInternalPromptsRouter = (opts) => {
|
|
|
85724
86222
|
summary: "List the box's own prompts and models",
|
|
85725
86223
|
description: "One row per model caller Stackbone itself runs on this box — the Studio copilot, the guardrail classifier, the eval judge, the persona simulator, Auto-map, memory extraction and the query rewrite. `prompt` says where its text is edited: a caller owned by an agent or a workflow names that catalog entry (its Prompts block edits it); a `system` caller carries the text compiled into this box as its default, the operator's PUBLISHED text when there is one (`null` means it runs on the default) and whether a prompt is stored at its address at all. `model` says where the model is picked and what it falls back to.\n\nRead-only. The text is written through `/api/prompts` (create or update, then publish; unpublish returns to the default), the copilot's and Auto-map's models through `/api/model-selections`, and the other settings-backed models through `/api/settings`.\n\nGated by `config:write` even though it is a read: it carries prompt bodies, and the seats that may not edit one were never shown it either.",
|
|
85726
86224
|
responses: {
|
|
85727
|
-
200: jsonResponse$
|
|
86225
|
+
200: jsonResponse$2(named("StudioInternalPromptListResponse", studioInternalPromptListResponseSchema), "Every internal caller, in the order the System screen lists them."),
|
|
85728
86226
|
403: forbiddenResponse("config:write")
|
|
85729
86227
|
}
|
|
85730
86228
|
}), async (c) => {
|
|
@@ -85814,7 +86312,7 @@ var createLogsRouter = (deps) => {
|
|
|
85814
86312
|
description: "The lines this box printed, read back out of its own database long after the ring buffer that streamed them has rolled over — this is what answers \"what did it print three weeks ago\", which the tail beside it cannot. Filters narrow the rows; a `q` text search additionally REQUIRES a `from`/`to` window, because searching log text is an unindexed scan. Pages are walked with `nextCursor`, and the rows carry the live tail's vocabulary (`msg`, `run_id`, `trace_id`) so one presentation layer renders both screens.",
|
|
85815
86313
|
request: { query: studioLogHistoryQuerySchema },
|
|
85816
86314
|
responses: {
|
|
85817
|
-
200: jsonResponse$
|
|
86315
|
+
200: jsonResponse$2(named("StudioLogHistoryResponse", studioLogHistoryResponseSchema), "One page of stored lines, plus the cursor the next page is asked with (`null` on the last)."),
|
|
85818
86316
|
400: errorResponse("The query string failed its schema (`invalid_payload`), or the WINDOW cannot be answered — `text_search_needs_range`, `time_range_too_wide`, `time_range_unreadable`, `time_range_inverted` — or the search itself came back refused: a cursor this box did not mint (`invalid_cursor`) or a scan that outran its deadline (`search_timed_out`). Never a 500: each is the reader's own search coming back with a sentence about how to narrow it."),
|
|
85819
86317
|
403: forbiddenResponse("logs:read"),
|
|
85820
86318
|
503: errorResponse("This build has no log database behind it (`logs_history_unavailable`). An empty page would read as \"this box has never printed anything\", which is a different and much worse diagnosis.")
|
|
@@ -86078,7 +86576,7 @@ var createMemoryRouter = (deps) => {
|
|
|
86078
86576
|
body: jsonBody(named("SearchStudioMemoryPeopleRequest", searchStudioMemoryPeopleRequestSchema), "The search text, the cursor of the previous page and the page size.")
|
|
86079
86577
|
},
|
|
86080
86578
|
responses: {
|
|
86081
|
-
200: jsonResponse$
|
|
86579
|
+
200: jsonResponse$2(named("StudioMemoryPersonListResponse", studioMemoryPersonListResponseSchema), "One page of people, their ids, fact counts and last call dates."),
|
|
86082
86580
|
400: errorResponse("The agent scope, the body or the cursor is invalid."),
|
|
86083
86581
|
403: forbiddenResponse("db:query")
|
|
86084
86582
|
}
|
|
@@ -86099,7 +86597,7 @@ var createMemoryRouter = (deps) => {
|
|
|
86099
86597
|
description: "Facts in stored order with their creation and update call provenance, followed by the person’s calls newest first.",
|
|
86100
86598
|
request: { params: studioMemoryPersonParamSchema },
|
|
86101
86599
|
responses: {
|
|
86102
|
-
200: jsonResponse$
|
|
86600
|
+
200: jsonResponse$2(named("StudioMemoryDetailResponse", studioMemoryDetailResponseSchema), "The facts and calls retained for this person."),
|
|
86103
86601
|
400: errorResponse("The agent scope or person id is invalid."),
|
|
86104
86602
|
404: errorResponse("No person has this id on this agent, or no facts or calls remain for them."),
|
|
86105
86603
|
403: forbiddenResponse("db:query")
|
|
@@ -86120,7 +86618,7 @@ var createMemoryRouter = (deps) => {
|
|
|
86120
86618
|
description: "The agent runs whose frozen identity is exactly this person, newest first. Another format of the same number, or a mention of it in a transcript, is another person.",
|
|
86121
86619
|
request: { params: studioMemoryPersonParamSchema },
|
|
86122
86620
|
responses: {
|
|
86123
|
-
200: jsonResponse$
|
|
86621
|
+
200: jsonResponse$2(named("StudioMemoryPersonRunsResponse", studioMemoryPersonRunsResponseSchema), "The person’s runs on this agent, with their status and creation date."),
|
|
86124
86622
|
400: errorResponse("The agent scope or person id is invalid."),
|
|
86125
86623
|
404: errorResponse("No person has this id on this agent."),
|
|
86126
86624
|
403: forbiddenResponse("db:query")
|
|
@@ -86141,7 +86639,7 @@ var createMemoryRouter = (deps) => {
|
|
|
86141
86639
|
description: "Deletes the person’s runs on this agent with their steps (the same runs `GET /agents/{agentId}/people/{personId}/runs` lists), the conversation checkpoints of those runs’ sessions, then every fact and call summary. The memory erase bumps the same revision tombstone as `stackbone.memory.deleteAll`, preventing an in-flight update from restoring erased data; `stackbone.memory.deleteAll` itself stays memory-only and leaves runs and checkpoints in place. The tombstone keeps only the opaque id: an erased id answers 404 here, on read and on its runs, and a person who comes back gets a new id.",
|
|
86142
86640
|
request: { params: studioMemoryPersonParamSchema },
|
|
86143
86641
|
responses: {
|
|
86144
|
-
200: jsonResponse$
|
|
86642
|
+
200: jsonResponse$2(named("DeleteStudioMemoryResponse", deleteStudioMemoryResponseSchema), "The number of fact and call rows removed, and the number of runs removed."),
|
|
86145
86643
|
400: errorResponse("The agent scope or person id is invalid."),
|
|
86146
86644
|
403: forbiddenResponse("rag:write"),
|
|
86147
86645
|
404: errorResponse("No person has this id on this agent."),
|
|
@@ -86411,7 +86909,7 @@ var response = async (agent, overrides, db) => ({
|
|
|
86411
86909
|
...resolveEffectiveMemorySettings(agent.memory, overrides)
|
|
86412
86910
|
});
|
|
86413
86911
|
var responses = {
|
|
86414
|
-
200: jsonResponse$
|
|
86912
|
+
200: jsonResponse$2(named("StudioMemorySettingsResponse", studioMemorySettingsResponseSchema), "The overrides and effective per-field values and origins."),
|
|
86415
86913
|
400: errorResponse("The agent or settings payload is invalid."),
|
|
86416
86914
|
404: errorResponse("The agent is not registered on this box.")
|
|
86417
86915
|
};
|
|
@@ -86564,7 +87062,7 @@ var createMeRouter = () => {
|
|
|
86564
87062
|
summary: "Read what the calling credential may do",
|
|
86565
87063
|
description: "The capability list of the credential this request arrived with — what Studio draws its owner/admin surfaces from — and its role, as a label the Studio copilot is told. A browser-direct caller gets the capabilities its identity token was signed with; a self-host operator and an ungated `stackbone dev` box get the full owner expansion, because on neither is there anything the box would refuse. Carries no capability gate of its own: it reveals nothing the caller does not already hold, and gating the answer on a capability would make the answer depend on the answer.",
|
|
86566
87064
|
responses: {
|
|
86567
|
-
200: jsonResponse$
|
|
87065
|
+
200: jsonResponse$2(named("CallerCapabilities", callerCapabilitiesSchema), "What this caller may do on this box."),
|
|
86568
87066
|
403: errorResponse("The credential is not a SEAT: a proxy signature, a workflow callback or a workspace API key. Each of those names a machine rather than a person, so there are no capabilities to report — an API key carries its own permission list, written on the key and checked by the invocation surface it was minted for.")
|
|
86569
87067
|
}
|
|
86570
87068
|
}), (c) => {
|
|
@@ -86689,6 +87187,68 @@ var resetFirstRun = async (opts) => {
|
|
|
86689
87187
|
await deleteSystemSecret(opts, FIRST_RUN_COMPLETED_SECRET);
|
|
86690
87188
|
};
|
|
86691
87189
|
//#endregion
|
|
87190
|
+
//#region ../../libs/runtime/workspace-runtime/src/lib/model-provider/livekit-test.ts
|
|
87191
|
+
init_src$18();
|
|
87192
|
+
init_livekit_token();
|
|
87193
|
+
init_error_message();
|
|
87194
|
+
init_provider_catalogues();
|
|
87195
|
+
/** Served by LiveKit itself, so a refusal is about the credentials, not an upstream. */
|
|
87196
|
+
var PROBE_MODEL = "google/gemma-4-31b-it";
|
|
87197
|
+
var testLiveKitInference = async (opts, fetchImpl = fetch) => {
|
|
87198
|
+
let credentials;
|
|
87199
|
+
try {
|
|
87200
|
+
credentials = await readLiveKitCredentials(opts);
|
|
87201
|
+
} catch (err) {
|
|
87202
|
+
return {
|
|
87203
|
+
ok: false,
|
|
87204
|
+
error: `The Voice credentials cannot be read (${messageOf$6(err)}). Save them again under Voice.`
|
|
87205
|
+
};
|
|
87206
|
+
}
|
|
87207
|
+
if (credentials === null) return {
|
|
87208
|
+
ok: false,
|
|
87209
|
+
error: "No LiveKit credentials are saved under Voice."
|
|
87210
|
+
};
|
|
87211
|
+
let response;
|
|
87212
|
+
try {
|
|
87213
|
+
response = await fetchImpl(`${LIVEKIT_INFERENCE_BASE_URL}/chat/completions`, {
|
|
87214
|
+
method: "POST",
|
|
87215
|
+
headers: {
|
|
87216
|
+
authorization: `Bearer ${await signLiveKitInferenceToken(credentials)}`,
|
|
87217
|
+
"content-type": "application/json"
|
|
87218
|
+
},
|
|
87219
|
+
body: JSON.stringify({
|
|
87220
|
+
model: PROBE_MODEL,
|
|
87221
|
+
max_tokens: 1,
|
|
87222
|
+
messages: [{
|
|
87223
|
+
role: "user",
|
|
87224
|
+
content: "ping"
|
|
87225
|
+
}]
|
|
87226
|
+
}),
|
|
87227
|
+
redirect: "manual",
|
|
87228
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS$1)
|
|
87229
|
+
});
|
|
87230
|
+
} catch (err) {
|
|
87231
|
+
return {
|
|
87232
|
+
ok: false,
|
|
87233
|
+
error: `LiveKit Inference could not be reached (${messageOf$6(err)}).`
|
|
87234
|
+
};
|
|
87235
|
+
}
|
|
87236
|
+
await response.body?.cancel();
|
|
87237
|
+
if (response.status === 401 || response.status === 403) return {
|
|
87238
|
+
ok: false,
|
|
87239
|
+
error: `LiveKit rejected the Voice credentials (HTTP ${response.status}). Check the API key and secret saved under Voice.`
|
|
87240
|
+
};
|
|
87241
|
+
if (!response.ok) return {
|
|
87242
|
+
ok: false,
|
|
87243
|
+
error: `LiveKit Inference answered HTTP ${response.status}.`
|
|
87244
|
+
};
|
|
87245
|
+
return {
|
|
87246
|
+
ok: true,
|
|
87247
|
+
model_count: fixedCatalogue("livekit", "https://agent-gateway.livekit.cloud/v1")?.length ?? 0,
|
|
87248
|
+
key_checked: "verified"
|
|
87249
|
+
};
|
|
87250
|
+
};
|
|
87251
|
+
//#endregion
|
|
86692
87252
|
//#region ../../libs/runtime/workspace-runtime/src/lib/model-provider/http/model-provider-handlers.ts
|
|
86693
87253
|
init_src$26();
|
|
86694
87254
|
init_provider_catalogues();
|
|
@@ -86735,13 +87295,15 @@ var STORE_REFUSAL_STATUS = {
|
|
|
86735
87295
|
invalid_id: 400,
|
|
86736
87296
|
not_found: 404,
|
|
86737
87297
|
duplicate: 409,
|
|
86738
|
-
default_provider: 409
|
|
87298
|
+
default_provider: 409,
|
|
87299
|
+
voice_provider: 409
|
|
86739
87300
|
};
|
|
86740
87301
|
var STORE_REFUSAL_MESSAGE = {
|
|
86741
87302
|
invalid_id: (id) => `'${id}' is not a provider id: lowercase letters, digits and dashes, 2 to 32, starting with a letter.`,
|
|
86742
87303
|
not_found: (id) => `No model provider '${id}' is configured on this box.`,
|
|
86743
87304
|
duplicate: (id) => `A model provider '${id}' already exists.`,
|
|
86744
|
-
default_provider: () => "Provider 1 is saved and removed by the model provider form itself, not by its id."
|
|
87305
|
+
default_provider: () => "Provider 1 is saved and removed by the model provider form itself, not by its id.",
|
|
87306
|
+
voice_provider: () => "LiveKit Inference is added by saving the LiveKit credentials under Voice, not here."
|
|
86745
87307
|
};
|
|
86746
87308
|
var storeRefusal = (err) => ({
|
|
86747
87309
|
code: `model_provider_${err.code}`,
|
|
@@ -86820,12 +87382,12 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
86820
87382
|
baseUrl: provider.baseUrl,
|
|
86821
87383
|
apiKey: maskedApiKey(provider.apiKey !== void 0),
|
|
86822
87384
|
source: provider.source,
|
|
86823
|
-
removable: !(provider.id === "default" && provider.source === "env"),
|
|
87385
|
+
removable: provider.source !== "voice" && !(provider.id === "default" && provider.source === "env"),
|
|
86824
87386
|
responding: read?.responding ?? null,
|
|
86825
87387
|
...read?.error === void 0 ? {} : { error: read.error }
|
|
86826
87388
|
};
|
|
86827
87389
|
}),
|
|
86828
|
-
duplicatedIds: duplicatedModelIds(orderedUnion(providers, catalogues))
|
|
87390
|
+
duplicatedIds: duplicatedModelIds(orderedUnion(providers.filter(claimsPlainModelIds), catalogues))
|
|
86829
87391
|
};
|
|
86830
87392
|
};
|
|
86831
87393
|
const listModels = async (body) => {
|
|
@@ -86869,7 +87431,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
86869
87431
|
tags: [MODEL_PROVIDER_TAG],
|
|
86870
87432
|
summary: "Read the model provider configuration",
|
|
86871
87433
|
description: "The stored base URL and a MASKED key — the plaintext never rides back through this surface. `source` says which of the two the runtime is actually resolving: a `MODEL_PROVIDER_*` variable in the box's boot environment outranks anything this screen holds, and saying so is the difference between a screen that lies and one an operator can act on.",
|
|
86872
|
-
responses: { 200: jsonResponse$
|
|
87434
|
+
responses: { 200: jsonResponse$2(modelProviderGetResponse, "The configuration, masked, and where the live value comes from.") }
|
|
86873
87435
|
}), async (c) => c.json(await getModelProvider(secretsOptions, bootEnv), 200));
|
|
86874
87436
|
app.openapi(createRoute({
|
|
86875
87437
|
method: "put",
|
|
@@ -86881,7 +87443,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
86881
87443
|
description: "Persists, then makes the credential LIVE on the running workspace, then re-registers whatever was degraded — in that order. Anything else and the operator is told the save worked while the workspace stays exactly as broken as it was, with a `stackbone dev` restart as the undocumented missing step. The reply carries the masked view and, when the runtime could act on it, how many agents came up.",
|
|
86882
87444
|
request: { body: jsonBody(modelProviderRequestBody, "The base URL, and the key when the gateway needs one.") },
|
|
86883
87445
|
responses: {
|
|
86884
|
-
200: jsonResponse$
|
|
87446
|
+
200: jsonResponse$2(modelProviderPutResponse, "The fresh masked view, plus what came up when the runtime could report it."),
|
|
86885
87447
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
86886
87448
|
403: forbiddenResponse("secrets:rotate")
|
|
86887
87449
|
}
|
|
@@ -86903,7 +87465,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
86903
87465
|
summary: "Remove the saved model provider",
|
|
86904
87466
|
description: "Deletes the base URL, key and gateway kind the Model provider screen stored, with the override flags beside them, and takes them off the running workspace. Every value the box exports is back in force at once, with no restart; the reply is the view afterwards, so `source` and `env_override` say what is still resolving and `overridden` is empty. The first-run marker is untouched.",
|
|
86905
87467
|
responses: {
|
|
86906
|
-
200: jsonResponse$
|
|
87468
|
+
200: jsonResponse$2(modelProviderGetResponse, "The view after the removal, naming whatever the environment still supplies."),
|
|
86907
87469
|
403: forbiddenResponse("secrets:rotate")
|
|
86908
87470
|
}
|
|
86909
87471
|
}), async (c) => {
|
|
@@ -86926,7 +87488,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
86926
87488
|
description: "Drops the override flag on one slot (`baseUrl` or `apiKey`), so the variable the box exports for it is back in force on the running workspace. The value this screen saved is kept, and wins again only if the variable is unset. The reply is the view afterwards; a slot that was not overridden answers the view unchanged and records nothing.",
|
|
86927
87489
|
request: { params: modelProviderOverrideSlotPath },
|
|
86928
87490
|
responses: {
|
|
86929
|
-
200: jsonResponse$
|
|
87491
|
+
200: jsonResponse$2(modelProviderGetResponse, "The view after the override was dropped, naming the variable back in force."),
|
|
86930
87492
|
400: errorResponse("The slot is neither `baseUrl` nor `apiKey` (`invalid_payload`)."),
|
|
86931
87493
|
403: forbiddenResponse("secrets:rotate")
|
|
86932
87494
|
}
|
|
@@ -86948,7 +87510,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
86948
87510
|
summary: "Continue without a provider",
|
|
86949
87511
|
description: "Writes NO credential. It records the decision and releases the boot gate, so the agents come up DEGRADED — registered, visible, with an actionable reason — rather than the process sitting there waiting for a browser. The provider view rides back unchanged (`configured: false`), so the screen can say plainly that nothing is configured and mean it.",
|
|
86950
87512
|
responses: {
|
|
86951
|
-
200: jsonResponse$
|
|
87513
|
+
200: jsonResponse$2(modelProviderPutResponse, "The unchanged view, plus what came up degraded."),
|
|
86952
87514
|
403: forbiddenResponse("secrets:rotate")
|
|
86953
87515
|
}
|
|
86954
87516
|
}), async (c) => {
|
|
@@ -86961,7 +87523,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
86961
87523
|
tags: [MODEL_PROVIDER_TAG],
|
|
86962
87524
|
summary: "Is this still the deployment’s first run?",
|
|
86963
87525
|
description: "A presence check on the marker, never a decrypted read. The guided screen asks before it starts walking somebody through a setup that is already done, and lands them on the ordinary configuration screen instead.",
|
|
86964
|
-
responses: { 200: jsonResponse$
|
|
87526
|
+
responses: { 200: jsonResponse$2(modelProviderFirstRunResponse, "Whether somebody has already been through the guided first run.") }
|
|
86965
87527
|
}), async (c) => {
|
|
86966
87528
|
const state = await readFirstRunState(secretsOptions);
|
|
86967
87529
|
return c.json(state, 200);
|
|
@@ -86974,7 +87536,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
86974
87536
|
summary: "Make the guided first run eligible again",
|
|
86975
87537
|
description: "Clears the MARKER only — the saved credential is untouched, because re-walking the setup must not log the operator out of the configuration they already have. Answers with the state it left behind so the screen can relaunch the flow without a second round-trip.",
|
|
86976
87538
|
responses: {
|
|
86977
|
-
200: jsonResponse$
|
|
87539
|
+
200: jsonResponse$2(modelProviderFirstRunResponse, "The marker, now cleared."),
|
|
86978
87540
|
403: forbiddenResponse("secrets:rotate")
|
|
86979
87541
|
}
|
|
86980
87542
|
}), async (c) => {
|
|
@@ -86991,7 +87553,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
86991
87553
|
description: "Calls `GET {base_url}/models` with the body's credential WITHOUT saving anything. A gateway that refuses rides back as `ok: false` DATA carrying a reason the operator can act on, never as an error status.",
|
|
86992
87554
|
request: { body: jsonBody(modelProviderRequestBody, "The candidate credential to probe.") },
|
|
86993
87555
|
responses: {
|
|
86994
|
-
200: jsonResponse$
|
|
87556
|
+
200: jsonResponse$2(modelProviderTestResponse, "Whether the gateway answered, and why not when it did not."),
|
|
86995
87557
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
86996
87558
|
403: forbiddenResponse("secrets:rotate")
|
|
86997
87559
|
}
|
|
@@ -87004,7 +87566,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
87004
87566
|
description: "A POST because the body may carry a DRAFT credential the operator has not saved yet. With a `base_url` it probes that endpoint; with an empty body it resolves the SAVED provider server-side, which is how the stored key stays out of the browser. With an empty body on a box with providers beside provider 1, it lists every provider's models, each tagged with the `provider` that serves it, and fails only when no provider answers.",
|
|
87005
87567
|
request: { body: jsonBody(named("ModelProviderModelsRequest", modelProviderModelsRequestSchema), "The draft endpoint to probe, or `{}` to use the saved providers.") },
|
|
87006
87568
|
responses: {
|
|
87007
|
-
200: jsonResponse$
|
|
87569
|
+
200: jsonResponse$2(named("ModelProviderModelsResponse", modelProviderModelsResponseSchema), "The models the providers advertise, normalized."),
|
|
87008
87570
|
400: errorResponse("The body failed its schema (`invalid_payload`).")
|
|
87009
87571
|
}
|
|
87010
87572
|
}), async (c) => c.json(await listModels(c.req.valid("json")), 200));
|
|
@@ -87014,7 +87576,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
87014
87576
|
tags: [MODEL_PROVIDER_TAG],
|
|
87015
87577
|
summary: "List the model providers",
|
|
87016
87578
|
description: "Provider 1 and every provider added beside it, provider 1 first, each catalogue read now. Keys are masked. `responding: false` is a provider whose read failed. `duplicatedIds` names every model id two providers serve: a call naming one is refused as ambiguous. Provider 1's own view stays `GET /api/model-provider`.",
|
|
87017
|
-
responses: { 200: jsonResponse$
|
|
87579
|
+
responses: { 200: jsonResponse$2(modelProviderList, "Every provider, masked, and the ids two serve.") }
|
|
87018
87580
|
}), async (c) => c.json(await providerList(), 200));
|
|
87019
87581
|
app.openapi(createRoute({
|
|
87020
87582
|
method: "post",
|
|
@@ -87026,10 +87588,10 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
87026
87588
|
description: "Saves a provider beside provider 1. From the next call on, a model only it lists is routed to it; a model another provider lists too is refused as ambiguous, which `duplicatedIds` in the reply names. The reply is the list.",
|
|
87027
87589
|
request: { body: jsonBody(named("ModelProviderCreateRequest", modelProviderCreateRequestSchema), "The id, name, gateway kind and endpoint, and the key when the gateway needs one.") },
|
|
87028
87590
|
responses: {
|
|
87029
|
-
201: jsonResponse$
|
|
87591
|
+
201: jsonResponse$2(modelProviderList, "The list with the new provider."),
|
|
87030
87592
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
87031
87593
|
403: forbiddenResponse("secrets:rotate"),
|
|
87032
|
-
409: errorResponse("A provider with this id exists (`model_provider_duplicate`).")
|
|
87594
|
+
409: errorResponse("A provider with this id exists (`model_provider_duplicate`), or it is LiveKit Inference, which Voice adds (`model_provider_voice_provider`).")
|
|
87033
87595
|
}
|
|
87034
87596
|
}), async (c) => {
|
|
87035
87597
|
const body = c.req.valid("json");
|
|
@@ -87052,7 +87614,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
87052
87614
|
body: jsonBody(named("ModelProviderUpdateRequest", modelProviderUpdateRequestSchema), "The name, gateway kind and endpoint, and the key only to replace or clear it.")
|
|
87053
87615
|
},
|
|
87054
87616
|
responses: {
|
|
87055
|
-
200: jsonResponse$
|
|
87617
|
+
200: jsonResponse$2(modelProviderList, "The list with the provider as saved."),
|
|
87056
87618
|
400: errorResponse("The body failed its schema (`invalid_payload`), or the id is not a slug (`model_provider_invalid_id`)."),
|
|
87057
87619
|
403: forbiddenResponse("secrets:rotate"),
|
|
87058
87620
|
404: errorResponse("No provider has this id (`model_provider_not_found`)."),
|
|
@@ -87082,7 +87644,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
87082
87644
|
description: "Deletes an extra provider and its key. Its models leave the list and stop routing at once. Provider 1 is removed by `DELETE /api/model-provider`, and one the box exports cannot be removed here (D6), so `default` is refused. The reply is the list.",
|
|
87083
87645
|
request: { params: modelProviderIdPath },
|
|
87084
87646
|
responses: {
|
|
87085
|
-
200: jsonResponse$
|
|
87647
|
+
200: jsonResponse$2(modelProviderList, "The list without the provider."),
|
|
87086
87648
|
400: errorResponse("The id is not a slug (`model_provider_invalid_id`)."),
|
|
87087
87649
|
403: forbiddenResponse("secrets:rotate"),
|
|
87088
87650
|
404: errorResponse("No provider has this id (`model_provider_not_found`)."),
|
|
@@ -87102,10 +87664,10 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
87102
87664
|
tags: [MODEL_PROVIDER_TAG],
|
|
87103
87665
|
middleware: [requireCapability("secrets:rotate")],
|
|
87104
87666
|
summary: "Probe one saved model provider",
|
|
87105
|
-
description: "Calls `GET {baseUrl}/models` on that provider alone, with its stored key, and nothing else. `default` probes provider 1 with the credential in effect. A gateway that refuses rides back as `ok: false` DATA, never as an error status.",
|
|
87667
|
+
description: "Calls `GET {baseUrl}/models` on that provider alone, with its stored key, and nothing else. `default` probes provider 1 with the credential in effect. LiveKit Inference, which has no key and lists nothing, is probed with a one-token chat signed with the Voice credentials. A gateway that refuses rides back as `ok: false` DATA, never as an error status.",
|
|
87106
87668
|
request: { params: modelProviderIdPath },
|
|
87107
87669
|
responses: {
|
|
87108
|
-
200: jsonResponse$
|
|
87670
|
+
200: jsonResponse$2(modelProviderTestResponse, "Whether the gateway answered, and why not when it did not."),
|
|
87109
87671
|
403: forbiddenResponse("secrets:rotate"),
|
|
87110
87672
|
404: errorResponse("No provider has this id (`model_provider_not_found`).")
|
|
87111
87673
|
}
|
|
@@ -87113,6 +87675,7 @@ var createModelProviderRouter = (secretsOptions, bootEnv = {}, live = {}) => {
|
|
|
87113
87675
|
const { id } = c.req.valid("param");
|
|
87114
87676
|
const provider = (await listProviders(secretsOptions, bootEnv)).find((p) => p.id === id);
|
|
87115
87677
|
if (!provider) return c.json(storeRefusal(new ProviderStoreError("not_found", id)), 404);
|
|
87678
|
+
if (provider.kind === "livekit") return c.json(await testLiveKitInference(secretsOptions), 200);
|
|
87116
87679
|
return c.json(await testModelProviderConnection({
|
|
87117
87680
|
base_url: provider.baseUrl,
|
|
87118
87681
|
api_key: provider.apiKey ?? null
|
|
@@ -87348,7 +87911,7 @@ var createModelSelectionsRouter = (opts) => {
|
|
|
87348
87911
|
tags: [MODEL_SELECTIONS_TAG],
|
|
87349
87912
|
summary: "List the model selections",
|
|
87350
87913
|
description: "Every model an operator picked on this box, of every owner, ordered by owner then key. A key with no row runs on its code default. Capped at 500 rows.",
|
|
87351
|
-
responses: { 200: jsonResponse$
|
|
87914
|
+
responses: { 200: jsonResponse$2(named("StudioModelSelectionListResponse", studioModelSelectionListResponseSchema), "Every pick on this box.") }
|
|
87352
87915
|
}), async (c) => {
|
|
87353
87916
|
const rows = await opts.db.select().from(modelSelections).orderBy(modelSelections.ownerKind, modelSelections.ownerName, modelSelections.key).limit(LIST_PAGE_SIZE);
|
|
87354
87917
|
return c.json({ items: rows.map(rowToSelection) }, 200);
|
|
@@ -87365,7 +87928,7 @@ var createModelSelectionsRouter = (opts) => {
|
|
|
87365
87928
|
body: jsonBody(named("PutStudioModelSelectionRequest", putStudioModelSelectionRequestSchema), "The model id, as the model provider names it, and optionally the provider it goes to.")
|
|
87366
87929
|
},
|
|
87367
87930
|
responses: {
|
|
87368
|
-
200: jsonResponse$
|
|
87931
|
+
200: jsonResponse$2(named("StudioModelSelection", studioModelSelectionSchema), "The pick as it now stands."),
|
|
87369
87932
|
400: errorResponse(`The body failed its schema, or ${PATH_REFUSAL}`),
|
|
87370
87933
|
403: forbiddenResponse("config:write")
|
|
87371
87934
|
}
|
|
@@ -87418,7 +87981,7 @@ var createModelSelectionsRouter = (opts) => {
|
|
|
87418
87981
|
description: "Deletes the pick, so the key runs on the code's default model again — or on the box's `defaultModel` when the code declares none.",
|
|
87419
87982
|
request: { params: studioModelSelectionPathSchema },
|
|
87420
87983
|
responses: {
|
|
87421
|
-
200: jsonResponse$
|
|
87984
|
+
200: jsonResponse$2(named("StudioModelSelectionDeleteResponse", studioModelSelectionDeleteResponseSchema), "The pick that was removed."),
|
|
87422
87985
|
400: errorResponse(PATH_REFUSAL),
|
|
87423
87986
|
403: forbiddenResponse("config:write"),
|
|
87424
87987
|
404: errorResponse("No model is picked for that owner and key (`studio_model_selection_not_found`).")
|
|
@@ -87453,7 +88016,7 @@ var createModelSelectionsRouter = (opts) => {
|
|
|
87453
88016
|
body: jsonBody(named("PutStudioModelRoutingRequest", putStudioModelRoutingRequestSchema), "The provider routing, or a plain sort.")
|
|
87454
88017
|
},
|
|
87455
88018
|
responses: {
|
|
87456
|
-
200: jsonResponse$
|
|
88019
|
+
200: jsonResponse$2(named("StudioModelRouting", studioModelRoutingSchema), "The routing as it now stands."),
|
|
87457
88020
|
400: errorResponse(`The body failed its schema, or ${ROUTING_PATH_REFUSAL}`),
|
|
87458
88021
|
403: forbiddenResponse("config:write")
|
|
87459
88022
|
}
|
|
@@ -87507,7 +88070,7 @@ var createModelSelectionsRouter = (opts) => {
|
|
|
87507
88070
|
description: "Deletes the routing, so the key runs on the code's `modelRouting` again: for a subagent with none, the agent's routing; else OpenRouter's default routing.",
|
|
87508
88071
|
request: { params: studioModelRoutingPathSchema },
|
|
87509
88072
|
responses: {
|
|
87510
|
-
200: jsonResponse$
|
|
88073
|
+
200: jsonResponse$2(named("StudioModelRoutingDeleteResponse", studioModelRoutingDeleteResponseSchema), "The routing that was removed."),
|
|
87511
88074
|
400: errorResponse(ROUTING_PATH_REFUSAL),
|
|
87512
88075
|
403: forbiddenResponse("config:write"),
|
|
87513
88076
|
404: errorResponse("No provider routing is picked for that agent and key (`studio_model_routing_not_found`).")
|
|
@@ -87567,7 +88130,7 @@ var createTelemetryExportRouter = (deps) => {
|
|
|
87567
88130
|
tags: [TELEMETRY_EXPORT_TAG],
|
|
87568
88131
|
summary: "Read the OTLP export posture",
|
|
87569
88132
|
description: "Whether an export endpoint is configured, whether the box is actually exporting, the one line the boot logged about it, and how many records never reached the collector. The endpoint itself is never in the answer — a collector URL routinely carries a token, and this read is open to every seat that can open settings.",
|
|
87570
|
-
responses: { 200: jsonResponse$
|
|
88133
|
+
responses: { 200: jsonResponse$2(named("TelemetryExportPosture", telemetryExportPostureSchema), "The export posture, and what the export has lost.") }
|
|
87571
88134
|
}), async (c) => {
|
|
87572
88135
|
const state = await deps.readState();
|
|
87573
88136
|
return c.json({
|
|
@@ -87696,7 +88259,7 @@ var createQueuesHandlers = () => {
|
|
|
87696
88259
|
tags: [AGENT_QUEUES_TAG],
|
|
87697
88260
|
summary: "List the agent-registered cron schedules (retired)",
|
|
87698
88261
|
description: "RETIRED, and deliberately empty FOREVER — not \"empty because nothing is scheduled\". This box's real recurring schedules are read at `GET /api/recurring-jobs`, which reports every declared cadence (workflow crons, one polling repeatable per intake link, the connection-refresh sweep) with its next fire time. Never wire a screen to this one.",
|
|
87699
|
-
responses: { 200: jsonResponse$
|
|
88262
|
+
responses: { 200: jsonResponse$2(named("ListSchedulesResponse", listSchedulesResponseSchema), "Always `{ \"items\": [] }`.") }
|
|
87700
88263
|
}), (c) => c.json({ items: [] }, 200));
|
|
87701
88264
|
const queues = new OpenAPIHono({ defaultHook: invalidPayloadHook });
|
|
87702
88265
|
queues.openapi(createRoute({
|
|
@@ -87705,7 +88268,7 @@ var createQueuesHandlers = () => {
|
|
|
87705
88268
|
tags: [QUEUES_TAG],
|
|
87706
88269
|
summary: "List the queues (retired)",
|
|
87707
88270
|
description: `The Queues Inspector sidebar. ${RETIRED} There are no queues to report, so the list is empty.`,
|
|
87708
|
-
responses: { 200: jsonResponse$
|
|
88271
|
+
responses: { 200: jsonResponse$2(named("QueueListResponse", queueListResponseSchema), "Always `{ \"items\": [] }`.") }
|
|
87709
88272
|
}), (c) => c.json({ items: [] }, 200));
|
|
87710
88273
|
queues.openapi(createRoute({
|
|
87711
88274
|
method: "get",
|
|
@@ -87718,7 +88281,7 @@ var createQueuesHandlers = () => {
|
|
|
87718
88281
|
query: peekQueueQuerySchema
|
|
87719
88282
|
},
|
|
87720
88283
|
responses: {
|
|
87721
|
-
200: jsonResponse$
|
|
88284
|
+
200: jsonResponse$2(named("PeekQueueResponse", peekQueueResponseSchema), "Always `{ \"items\": [] }`."),
|
|
87722
88285
|
400: errorResponse("The queue name is not a BullMQ queue identifier, or the `limit` is outside [1, 50] (`invalid_payload`).")
|
|
87723
88286
|
}
|
|
87724
88287
|
}), (c) => c.json({ items: [] }, 200));
|
|
@@ -87732,7 +88295,7 @@ var createQueuesHandlers = () => {
|
|
|
87732
88295
|
description: `Drops every removable job — an active one stays, a worker holds it. ${RETIRED} There is nothing to remove, so the count is zero.`,
|
|
87733
88296
|
request: { params: queueNameParamsSchema },
|
|
87734
88297
|
responses: {
|
|
87735
|
-
200: jsonResponse$
|
|
88298
|
+
200: jsonResponse$2(named("PurgeQueueResponse", purgeQueueResponseSchema), "Always `{ \"purged\": 0 }`."),
|
|
87736
88299
|
400: errorResponse("The queue name is not a BullMQ queue identifier (`invalid_payload`)."),
|
|
87737
88300
|
403: forbiddenResponse("runs:invoke")
|
|
87738
88301
|
}
|
|
@@ -87763,7 +88326,7 @@ var createQueuesHandlers = () => {
|
|
|
87763
88326
|
description: `The Jobs table. \`queue\` is REQUIRED — a BullMQ queue scopes its jobs, so there is no "all jobs" to list. ${RETIRED} There are no jobs to report, so the page is empty.`,
|
|
87764
88327
|
request: { query: listJobsQuerySchema },
|
|
87765
88328
|
responses: {
|
|
87766
|
-
200: jsonResponse$
|
|
88329
|
+
200: jsonResponse$2(named("JobListResponse", jobListResponseSchema), "Always `{ \"items\": [], \"nextCursor\": null }`."),
|
|
87767
88330
|
400: errorResponse("The query failed its schema — a missing or malformed `queue` included (`invalid_payload`).")
|
|
87768
88331
|
}
|
|
87769
88332
|
}), (c) => c.json({
|
|
@@ -89723,7 +90286,7 @@ function mountRagRebuildRoutes(app, resolve) {
|
|
|
89723
90286
|
summary: "Read collection rebuild status",
|
|
89724
90287
|
request: { params },
|
|
89725
90288
|
responses: {
|
|
89726
|
-
200: jsonResponse$
|
|
90289
|
+
200: jsonResponse$2(named("RagRebuildStatusResponse", ragRebuildStatusResponseSchema), "Current and target identity, World lifecycle and retained progress; null before the first rebuild."),
|
|
89727
90290
|
400: errors$1[400],
|
|
89728
90291
|
404: errors$1[404],
|
|
89729
90292
|
409: errors$1[409],
|
|
@@ -89756,7 +90319,7 @@ function mountRagRebuildRoutes(app, resolve) {
|
|
|
89756
90319
|
body: jsonBody(named("ChangeRagEmbeddingRequest", changeRagEmbeddingRequestSchema), "Target provider and model.")
|
|
89757
90320
|
},
|
|
89758
90321
|
responses: {
|
|
89759
|
-
202: jsonResponse$
|
|
90322
|
+
202: jsonResponse$2(named("RagRebuildStatus", ragRebuildStatusSchema), "Accepted logical rebuild and its current durable state."),
|
|
89760
90323
|
...errors$1
|
|
89761
90324
|
}
|
|
89762
90325
|
}), async (c) => {
|
|
@@ -89785,7 +90348,7 @@ function mountRagRebuildRoutes(app, resolve) {
|
|
|
89785
90348
|
body: jsonBody(named("RetryRagRebuildRequest", retryRagRebuildRequestSchema), "The logical rebuild to resume.")
|
|
89786
90349
|
},
|
|
89787
90350
|
responses: {
|
|
89788
|
-
202: jsonResponse$
|
|
90351
|
+
202: jsonResponse$2(named("RagRebuildStatus", ragRebuildStatusSchema), "Same logical rebuild, new fenced attempt."),
|
|
89789
90352
|
...errors$1
|
|
89790
90353
|
}
|
|
89791
90354
|
}), async (c) => {
|
|
@@ -89951,7 +90514,7 @@ var createRagRouter = (deps) => {
|
|
|
89951
90514
|
summary: "List the collections in this agent",
|
|
89952
90515
|
description: "Every row of `stackbone_rag_collections`, with the counts and the index metadata the Studio explorer renders. The counts come from the documents and chunks tables, so an empty collection created before its first ingest reads `0` with a null `last_ingested_at`.",
|
|
89953
90516
|
responses: {
|
|
89954
|
-
200: jsonResponse$
|
|
90517
|
+
200: jsonResponse$2(named("RagCollectionListResponse", ragCollectionListResponseSchema), "The collections, each with its dimensions, distance metric and counts."),
|
|
89955
90518
|
409: errorResponse("The canonical `stackbone_rag_*` schema is not installed on this agent (`rag_schema_missing`). Studio gates its write affordances off THIS read rather than on an empty list, so the refusal is structured instead of an empty page.")
|
|
89956
90519
|
}
|
|
89957
90520
|
}), async (c) => {
|
|
@@ -89974,7 +90537,7 @@ var createRagRouter = (deps) => {
|
|
|
89974
90537
|
query: listRagDocsQuerySchema
|
|
89975
90538
|
},
|
|
89976
90539
|
responses: {
|
|
89977
|
-
200: jsonResponse$
|
|
90540
|
+
200: jsonResponse$2(named("RagDocumentListResponse", ragDocumentListResponseSchema), "One page of documents, and the cursor for the next."),
|
|
89978
90541
|
400: errorResponse("The query string failed its schema, or `filter` is not a JSON object this store can match (`invalid_payload`)."),
|
|
89979
90542
|
404: errorResponse("The `{name}` segment is not a collection name this box can read (`rag_collection_not_found`).")
|
|
89980
90543
|
}
|
|
@@ -90006,7 +90569,7 @@ var createRagRouter = (deps) => {
|
|
|
90006
90569
|
body: jsonBody(named("RagQueryRequest", ragQueryRequestSchema), "The query: `text` to embed server-side, or an `embedding` to search with directly, optional metadata `filter` and typed `ranges`, `hybrid: false` to search by meaning only, and optionally a `preview` that searches as if that version of a document were published.")
|
|
90007
90570
|
},
|
|
90008
90571
|
responses: {
|
|
90009
|
-
200: jsonResponse$
|
|
90572
|
+
200: jsonResponse$2(named("RagQueryResponse", ragQueryResponseSchema), "The hits, closest first (in fused order on a hybrid search), with the dimensions searched at and the model that embedded the query (null when the caller supplied the vector)."),
|
|
90010
90573
|
400: errorResponse("Invalid metadata `ranges` are `rag_invalid_request`. Other body schema failures, including neither `text` nor `embedding` or an invalid `preview`, remain `invalid_payload`. An `embedding` with a different width than the collection returns `rag_dim_mismatch`, with `expected` and `received` under `details`."),
|
|
90011
90574
|
404: errorResponse("No such collection, or this agent has no RAG schema at all (`rag_collection_not_found`) — or `preview` names no document in it (`rag_document_not_found`) or a version that document never had (`rag_document_version_not_found`)."),
|
|
90012
90575
|
409: errorResponse("The explicit model conflicts with the collection (`rag_embedding_conflict`), or its embeddings are rebuilding or failed (`rag_collection_unavailable`)."),
|
|
@@ -90066,7 +90629,7 @@ var createRagRouter = (deps) => {
|
|
|
90066
90629
|
description: "The jobs an upload enqueues, newest first. Since feature 76 an ingest is a durable `rag-ingest` WORKFLOW RUN rather than a `rag_jobs` row, so this projects those runs onto the job shape the CLI and Studio have always read — same field names, same status vocabulary. `attempts` is therefore always 0: a retry starts a NEW run rather than incrementing a counter on this one.\n\n`collection` filters the page AFTER it is read (the runs query filters by entity, not by the workflow input), so a page can come back shorter than `limit` with a cursor still set.",
|
|
90067
90630
|
request: { query: listRagJobsQuerySchema },
|
|
90068
90631
|
responses: {
|
|
90069
|
-
200: jsonResponse$
|
|
90632
|
+
200: jsonResponse$2(named("RagJobListResponse", ragJobListResponseSchema.extend({ items: z.array(RAG_JOB) })), "One page of ingest jobs, and the cursor for the next."),
|
|
90070
90633
|
400: errorResponse("The query string failed its schema (`invalid_payload`).")
|
|
90071
90634
|
}
|
|
90072
90635
|
}), async (c) => {
|
|
@@ -90094,7 +90657,7 @@ var createRagRouter = (deps) => {
|
|
|
90094
90657
|
description: "Flips the ingest RUN `running → cancelled`, closing its open steps. The status is final: nothing reopens the run. State-only, like the generic runs cancel: the World worker carries no live cancel signal yet, so an ingest that is ACTIVELY mid-flight keeps executing in the background, and whatever it writes afterwards does not change the job. A run that is not a `rag-ingest` workflow run answers 404 and keeps running.",
|
|
90095
90658
|
request: { params: jobPath },
|
|
90096
90659
|
responses: {
|
|
90097
|
-
200: jsonResponse$
|
|
90660
|
+
200: jsonResponse$2(RAG_JOB_ACTION_RESPONSE, "The job and the status it now holds."),
|
|
90098
90661
|
403: forbiddenResponse("rag:write", "rag:review"),
|
|
90099
90662
|
404: errorResponse("No such ingest run, a run that is not one, or a `{jobId}` this box cannot read as an id (`rag_job_not_found`)."),
|
|
90100
90663
|
409: errorResponse("The job already reached a terminal state (`rag_job_not_cancelable`), with the state it holds under `details.state`.")
|
|
@@ -90128,7 +90691,7 @@ var createRagRouter = (deps) => {
|
|
|
90128
90691
|
description: "Creates an empty collection. With embedding_provider and embedding_model, probes that saved provider through the box, derives a valid storage dimension and initializes its search mode before saving. Provider and probe failures save nothing. Name-only requests keep the default provider, openai/text-embedding-3-small and 1536 dimensions without a network probe, unless the model provider is known not to serve that model: then the collection gets the first embedding model it serves, pinned to its provider, and that model is probed for its dimension. When the model provider lists neither that model nor any embedding model, a name-only request is refused and saves nothing. hybrid_enabled turns on hybrid search (meaning plus exact terms such as product codes) and number_format (eu or us) sets how it reads numbers and dates; omitted, hybrid search is off and the format is eu. extract_enabled defaults to false and extract_fields to an empty list; each field has a name, a number/date/text type and an optional removed flag.",
|
|
90129
90692
|
request: { body: jsonBody(named("CreateRagCollectionRequest", createRagCollectionRequestSchema), "The collection name, any org-defined metadata to hang on it, its embedding model and its hybrid search settings.") },
|
|
90130
90693
|
responses: {
|
|
90131
|
-
201: jsonResponse$
|
|
90694
|
+
201: jsonResponse$2(named("CreateRagCollectionResponse", createRagCollectionResponseSchema), "The collection as the list endpoint would report it, with zero counts."),
|
|
90132
90695
|
400: errorResponse("The body failed its schema (`invalid_payload`), a collection of that name already exists (`rag_collection_already_exists`), or the request names no embedding model and the model provider lists none (`rag_embedding_model_required`: pass embedding_provider and embedding_model). The duplicate is a 400 and NOT a 409, unlike its already-exists siblings on this surface — the status has been 400 since the cloud surface this mirrors, and Studio keys on the code."),
|
|
90133
90696
|
403: forbiddenResponse("rag:write"),
|
|
90134
90697
|
502: errorResponse("The selected provider/model could not produce a valid embedding (`rag_embedding_probe_failed`), including missing/down providers, timeout or an invalid/storage-incompatible vector."),
|
|
@@ -90156,7 +90719,7 @@ var createRagRouter = (deps) => {
|
|
|
90156
90719
|
body: jsonBody(named("UpdateRagCollectionRequest", updateRagCollectionRequestSchema), "The settings to change. At least one of them; an omitted one keeps its value.")
|
|
90157
90720
|
},
|
|
90158
90721
|
responses: {
|
|
90159
|
-
200: jsonResponse$
|
|
90722
|
+
200: jsonResponse$2(named("UpdateRagCollectionResponse", updateRagCollectionResponseSchema), "The saved collection settings; extraction changes also report unconverted."),
|
|
90160
90723
|
400: errorResponse("The body is empty, names an unknown key, number_format or field type (`invalid_payload`)."),
|
|
90161
90724
|
403: forbiddenResponse("rag:write"),
|
|
90162
90725
|
404: errorResponse("No such collection, or a `{name}` this box cannot read as a collection name (`rag_collection_not_found`). A box older than this route answers 404 `not_found` instead."),
|
|
@@ -90184,7 +90747,7 @@ var createRagRouter = (deps) => {
|
|
|
90184
90747
|
description: "Drops the collection with its documents, its chunks and the originals staged under the reserved `rag/` storage prefix. Not reversible, and not a soft delete.",
|
|
90185
90748
|
request: { params: collectionPath },
|
|
90186
90749
|
responses: {
|
|
90187
|
-
200: jsonResponse$
|
|
90750
|
+
200: jsonResponse$2(named("DeleteRagCollectionResponse", deleteRagCollectionResponseSchema), "The name that was dropped."),
|
|
90188
90751
|
403: forbiddenResponse("rag:write"),
|
|
90189
90752
|
404: errorResponse("No such collection, or a `{name}` this box cannot read as a collection name (`rag_collection_not_found`)."),
|
|
90190
90753
|
409: errorResponse("The canonical `stackbone_rag_*` schema is not installed on this agent (`rag_schema_missing`), or this collection is paused (`rag_collection_unavailable`).")
|
|
@@ -90212,7 +90775,7 @@ var createRagRouter = (deps) => {
|
|
|
90212
90775
|
body: multipartBody(named("UploadRagDocumentForm", uploadFormSchema$1), "The source bytes, the name to file them under, and their content type.")
|
|
90213
90776
|
},
|
|
90214
90777
|
responses: {
|
|
90215
|
-
200: jsonResponse$
|
|
90778
|
+
200: jsonResponse$2(named("UploadRagDocumentResponse", uploadRagDocumentResponseSchema), "The enqueued ingest job: the id to poll, the status it starts in, and the version the upload became."),
|
|
90216
90779
|
400: errorResponse("The body is not `multipart/form-data`, or it is missing `file`, `filename` or `content_type` — or sent `file` as ordinary text, or a `content_type` outside the three supported source formats, or a `metadata` that parses to something other than a JSON object (`invalid_payload`)."),
|
|
90217
90780
|
403: errorResponse("The caller's role grants neither `rag:write` nor `rag:review` (`forbidden`), or grants `rag:review` alone and the form has no `replaces`: only `rag:write` starts a new document. Only reachable on the browser-direct datapath — a proxy-signed call was authorized before it was signed."),
|
|
90218
90781
|
404: errorResponse("No such collection, or a `{name}` this box cannot read as a collection name (`rag_collection_not_found`) — or `replaces` names no document in it (`rag_document_not_found`)."),
|
|
@@ -90261,7 +90824,7 @@ var createRagRouter = (deps) => {
|
|
|
90261
90824
|
description: "Removes the document with every version of it, whichever version `{docId}` names: every chunk they produced and every original staged under the reserved `rag/` prefix. The collection itself stays.",
|
|
90262
90825
|
request: { params: collectionDocPath },
|
|
90263
90826
|
responses: {
|
|
90264
|
-
200: jsonResponse$
|
|
90827
|
+
200: jsonResponse$2(named("DeleteRagDocumentResponse", deleteRagDocumentResponseSchema), "The document id that was removed, and the collection it left."),
|
|
90265
90828
|
403: forbiddenResponse("rag:write"),
|
|
90266
90829
|
404: errorResponse("No such collection or a `{name}` this box cannot read (`rag_collection_not_found`), or no such document in it (`rag_document_not_found`)."),
|
|
90267
90830
|
409: errorResponse("The canonical `stackbone_rag_*` schema is not installed on this agent (`rag_schema_missing`), or this collection is paused (`rag_collection_unavailable`).")
|
|
@@ -90287,7 +90850,7 @@ var createRagRouter = (deps) => {
|
|
|
90287
90850
|
description: "Mints a short-lived signed URL for the original staged under the reserved `rag/` prefix, plus the filename and content type the browser needs to name the download. A POST rather than a GET because it MINTS a credential; it reads nothing back into the response body.",
|
|
90288
90851
|
request: { params: collectionDocPath },
|
|
90289
90852
|
responses: {
|
|
90290
|
-
200: jsonResponse$
|
|
90853
|
+
200: jsonResponse$2(named("DownloadRagDocumentResponse", downloadRagDocumentResponseSchema), "The signed URL and the metadata to name the file with."),
|
|
90291
90854
|
403: forbiddenResponse("rag:write", "rag:review"),
|
|
90292
90855
|
404: errorResponse("No such collection or a `{name}` this box cannot read (`rag_collection_not_found`), or no such document in it (`rag_document_not_found`)."),
|
|
90293
90856
|
409: errorResponse("The canonical `stackbone_rag_*` schema is not installed on this agent (`rag_schema_missing`).")
|
|
@@ -90316,7 +90879,7 @@ var createRagRouter = (deps) => {
|
|
|
90316
90879
|
body: jsonBody(named("ExtractRagDocumentRequest", extractRagDocumentRequestSchema), "An optional document version; omitted selects the published version.")
|
|
90317
90880
|
},
|
|
90318
90881
|
responses: {
|
|
90319
|
-
200: jsonResponse$
|
|
90882
|
+
200: jsonResponse$2(named("ExtractRagDocumentResponse", extractRagDocumentResponseSchema), "The proposal, whether extraction failed, and whether the middle was skipped."),
|
|
90320
90883
|
400: errorResponse("The request body failed schema validation (invalid_payload)."),
|
|
90321
90884
|
403: forbiddenResponse("rag:write"),
|
|
90322
90885
|
404: errorResponse("No such collection (rag_collection_not_found), document (rag_document_not_found), or version (rag_document_version_not_found)."),
|
|
@@ -90351,7 +90914,7 @@ var createRagRouter = (deps) => {
|
|
|
90351
90914
|
body: jsonBody(named("UpdateRagDocumentValuesRequest", updateRagDocumentValuesRequestSchema), "The values to merge, an optional version, and optional field definitions to add.")
|
|
90352
90915
|
},
|
|
90353
90916
|
responses: {
|
|
90354
|
-
200: jsonResponse$
|
|
90917
|
+
200: jsonResponse$2(named("UpdateRagDocumentValuesResponse", updateRagDocumentValuesResponseSchema), "The saved version and its merged metadata."),
|
|
90355
90918
|
400: errorResponse("A malformed body is invalid_payload; an unknown field or a value that does not match its field type is rag_invalid_request."),
|
|
90356
90919
|
403: forbiddenResponse("rag:write"),
|
|
90357
90920
|
404: errorResponse("No such collection (rag_collection_not_found), document (rag_document_not_found), or requested or published version (rag_document_version_not_found)."),
|
|
@@ -90377,7 +90940,7 @@ var createRagRouter = (deps) => {
|
|
|
90377
90940
|
description: "Every version of the document `{docId}` names (the id of any one of its versions), newest first, in one page. `is_active` marks the version the agent reads. `ready` says whether publishing a version is accepted: it has chunks, and its newest ingest, if any, succeeded. `ingest_status` alone does not say that, because an ingest can succeed and write no chunk.",
|
|
90378
90941
|
request: { params: collectionDocPath },
|
|
90379
90942
|
responses: {
|
|
90380
|
-
200: jsonResponse$
|
|
90943
|
+
200: jsonResponse$2(named("RagDocumentVersionListResponse", ragDocumentVersionListResponseSchema), "Every version of the document, newest first."),
|
|
90381
90944
|
404: errorResponse("No such collection or a `{name}` this box cannot read (`rag_collection_not_found`), or no such document in it (`rag_document_not_found`)."),
|
|
90382
90945
|
409: errorResponse("The canonical `stackbone_rag_*` schema is not installed on this agent (`rag_schema_missing`).")
|
|
90383
90946
|
}
|
|
@@ -90403,7 +90966,7 @@ var createRagRouter = (deps) => {
|
|
|
90403
90966
|
description: "Makes `{version}` the version of the document the agent reads, `{docId}` being the id of any of its versions. Publishing an earlier version is the rollback. Every version keeps its own chunks, so nothing is re-embedded: the published flag moves under a lock on the document, and the version it leaves stays in the history.\n\nPublishing the version already published changes nothing and answers `changed: false`. A version with no chunks, or whose newest ingest did not succeed, is refused: publishing it would take the document away from the agent.",
|
|
90404
90967
|
request: { params: collectionDocVersionPath },
|
|
90405
90968
|
responses: {
|
|
90406
|
-
200: jsonResponse$
|
|
90969
|
+
200: jsonResponse$2(named("ActivateRagDocumentVersionResponse", activateRagDocumentVersionResponseSchema), "The version now published, the one published before, and whether anything changed."),
|
|
90407
90970
|
403: forbiddenResponse("rag:write"),
|
|
90408
90971
|
404: errorResponse("No such collection or a `{name}` this box cannot read (`rag_collection_not_found`), no such document in it (`rag_document_not_found`), or no such version of it, a `{version}` that is not a positive integer included (`rag_document_version_not_found`)."),
|
|
90409
90972
|
409: errorResponse("The version is not ready to publish: it has no chunks, or its newest ingest did not succeed (`rag_version_not_ready`, with that `version` under `details`). Or the canonical `stackbone_rag_*` schema is not installed on this agent (`rag_schema_missing`). A paused collection answers `rag_collection_unavailable`.")
|
|
@@ -90432,7 +90995,7 @@ var createRagRouter = (deps) => {
|
|
|
90432
90995
|
description: "Removes `{version}` of the document `{docId}` names (the id of any of its versions): its row, every chunk it produced and its original staged under the reserved `rag/` prefix. The published version and every other version stay. The published version is refused: publish another one first, or delete the whole document.",
|
|
90433
90996
|
request: { params: collectionDocVersionPath },
|
|
90434
90997
|
responses: {
|
|
90435
|
-
200: jsonResponse$
|
|
90998
|
+
200: jsonResponse$2(named("DeleteRagDocumentVersionResponse", deleteRagDocumentVersionResponseSchema), "The id of the version that was removed, its number, and the collection it left."),
|
|
90436
90999
|
403: forbiddenResponse("rag:write", "rag:review"),
|
|
90437
91000
|
404: errorResponse("No such collection or a `{name}` this box cannot read (`rag_collection_not_found`), no such document in it (`rag_document_not_found`), or no such version of it, a `{version}` that is not a positive integer included (`rag_document_version_not_found`)."),
|
|
90438
91001
|
409: errorResponse("The version is the published one (`rag_version_active`), or its ingest is still queued or running (`rag_version_ingesting`), each with that `version` under `details`. Or the canonical `stackbone_rag_*` schema is not installed on this agent (`rag_schema_missing`). A paused collection answers `rag_collection_unavailable`.")
|
|
@@ -90460,7 +91023,7 @@ var createRagRouter = (deps) => {
|
|
|
90460
91023
|
description: "RE-RUNS the `rag-ingest` workflow over the failed run's stored input — the original is still staged under the reserved `rag/` prefix — so this is an actual re-ingest and not a row flip. Idempotent: the workflow's persist replaces the document in place. The answer carries the NEW run's id; the failed one keeps its own history. A run that is not a `rag-ingest` workflow run answers 404 and starts nothing.",
|
|
90461
91024
|
request: { params: jobPath },
|
|
90462
91025
|
responses: {
|
|
90463
|
-
200: jsonResponse$
|
|
91026
|
+
200: jsonResponse$2(RAG_JOB_ACTION_RESPONSE, "The NEW ingest run, queued."),
|
|
90464
91027
|
403: forbiddenResponse("rag:write", "rag:review"),
|
|
90465
91028
|
404: errorResponse("No such ingest run, a run that is not one, or a `{jobId}` this box cannot read as an id (`rag_job_not_found`)."),
|
|
90466
91029
|
409: errorResponse("The run did not fail, so there is nothing to retry (`rag_job_not_retryable`, with the state it holds under `details.state`) — or it failed but its stored input is incomplete, or the `rag-ingest` workflow runtime is not armed on this box, or the collection is paused (`rag_collection_unavailable`).")
|
|
@@ -90811,7 +91374,7 @@ var createRunsRouter = (deps) => {
|
|
|
90811
91374
|
description: "Cursor-paginated, newest first unless `order=asc`, filterable by status, trigger, entity name and kind, session, a `created_at` window and one `key:value` run tag (`tag`, an exact match on `metadata.tags`); `system=exclude` leaves out the runs of the entries the platform installed for itself. A pure READ: listing an open run never reconciles it — closing a finished workflow run is the background sweep's job, so this route never contends with the projector's writes.",
|
|
90812
91375
|
request: { query: listRunsQuery },
|
|
90813
91376
|
responses: {
|
|
90814
|
-
200: jsonResponse$
|
|
91377
|
+
200: jsonResponse$2(named("RunListResponse", runListResponseSchema), "One page of runs, and the cursors either side of it."),
|
|
90815
91378
|
400: errorResponse("The query failed its schema (`invalid_payload`), or the cursor is not one this box issued (`invalid_cursor`).")
|
|
90816
91379
|
}
|
|
90817
91380
|
}), async (c) => {
|
|
@@ -90832,7 +91395,7 @@ var createRunsRouter = (deps) => {
|
|
|
90832
91395
|
description: "The distinct entities present in this box's runs, feeding the Runs screen's name dropdown. TWO lists off ONE read so they can never disagree: `entries` keys each entity by (name, kind) — two same-named entries of different kinds are two rows — and `names` keeps the flat, pre-`entity_kind` shape for a Studio bundle that predates the kind. An entity that has never run does not appear. `system=exclude` leaves out the entries the platform installed for itself, as the list read does.",
|
|
90833
91396
|
request: { query: runEntityNamesQuerySchema },
|
|
90834
91397
|
responses: {
|
|
90835
|
-
200: jsonResponse$
|
|
91398
|
+
200: jsonResponse$2(named("RunEntityNamesResponse", runEntityNamesResponseSchema), "Every entity name present in the runs, and the (name, kind) pairs behind them."),
|
|
90836
91399
|
400: errorResponse("The query failed its schema (`invalid_payload`).")
|
|
90837
91400
|
}
|
|
90838
91401
|
}), async (c) => {
|
|
@@ -90851,7 +91414,7 @@ var createRunsRouter = (deps) => {
|
|
|
90851
91414
|
description: "The run row as it now stands: status, trigger, timings, the input it started from, the output or error it ended with, and the token counters a chat turn folded onto it.",
|
|
90852
91415
|
request: { params: runIdPath },
|
|
90853
91416
|
responses: {
|
|
90854
|
-
200: jsonResponse$
|
|
91417
|
+
200: jsonResponse$2(named("Run", runSchema), "The run as it now stands."),
|
|
90855
91418
|
404: errorResponse("No such run (`run_not_found`) — or an id that is not an id at all.")
|
|
90856
91419
|
}
|
|
90857
91420
|
}), async (c) => {
|
|
@@ -90870,7 +91433,7 @@ var createRunsRouter = (deps) => {
|
|
|
90870
91433
|
query: runStepsQuerySchema
|
|
90871
91434
|
},
|
|
90872
91435
|
responses: {
|
|
90873
|
-
200: jsonResponse$
|
|
91436
|
+
200: jsonResponse$2(named("RunStepsResponse", runStepsResponseSchema), "One page of the run's steps, and the cursors either side of it."),
|
|
90874
91437
|
400: errorResponse("The query failed its schema (`invalid_payload`), or the cursor is not one this box issued (`invalid_cursor`).")
|
|
90875
91438
|
}
|
|
90876
91439
|
}), async (c) => {
|
|
@@ -90896,7 +91459,7 @@ var createRunsRouter = (deps) => {
|
|
|
90896
91459
|
description: "One step with its payloads. The steps list leaves a `model_call`'s `input` and `output` out, because each is the whole prompt and reply of that call, stored uncapped; this is where they are read.",
|
|
90897
91460
|
request: { params: runStepPath },
|
|
90898
91461
|
responses: {
|
|
90899
|
-
200: jsonResponse$
|
|
91462
|
+
200: jsonResponse$2(named("RunStep", runStepSchema), "The step, payloads included."),
|
|
90900
91463
|
404: errorResponse("No such step in this run (`run_step_not_found`) — or an id that is not an id at all.")
|
|
90901
91464
|
}
|
|
90902
91465
|
}), async (c) => {
|
|
@@ -90915,7 +91478,7 @@ var createRunsRouter = (deps) => {
|
|
|
90915
91478
|
description: "Flips the row to `cancelled`, closes its open steps in the same transaction, and publishes `run.finished`. `cancelled` is final: a later close from the worker, a resume or the reconcile sweep does not change it. The SDK has no live cancel signal yet, so this closes the ROW — the worker behind it may keep running until it finishes on its own.",
|
|
90916
91479
|
request: { params: runIdPath },
|
|
90917
91480
|
responses: {
|
|
90918
|
-
200: jsonResponse$
|
|
91481
|
+
200: jsonResponse$2(named("CancelRunResponse", cancelRunResponseSchema), "The run as it now stands, `cancelled`."),
|
|
90919
91482
|
403: forbiddenResponse("runs:invoke"),
|
|
90920
91483
|
404: errorResponse("No such run (`run_not_found`)."),
|
|
90921
91484
|
409: errorResponse("The run is not running, so there is nothing to cancel (`run_not_running`).")
|
|
@@ -91088,7 +91651,7 @@ var createRecurringJobsRouter = (options) => {
|
|
|
91088
91651
|
tags: [RECURRING_JOBS_TAG],
|
|
91089
91652
|
summary: "List the box's recurring jobs",
|
|
91090
91653
|
description: "One item per DECLARED cadence — a workflow cron, an intake link's polling repeatable, the connection-refresh sweep — never one per repeatable Redis happens to hold, so a job whose repeatable failed to arm is reported here as inactive. A box with nothing armed yet answers an empty list, not an error.",
|
|
91091
|
-
responses: { 200: jsonResponse$
|
|
91654
|
+
responses: { 200: jsonResponse$2(named("RecurringJobsResponse", recurringJobsResponseSchema), "Every declared recurring job, plus the box's own clock.") }
|
|
91092
91655
|
}), async (c) => {
|
|
91093
91656
|
const registry = options.resolveRecurringJobs();
|
|
91094
91657
|
const items = registry ? (await registry.list()).map(toWire) : [];
|
|
@@ -91109,7 +91672,7 @@ var createRecurringJobsRouter = (options) => {
|
|
|
91109
91672
|
description: paused ? "Stops every recurring job carrying `pause_key` — every cron of one workflow for a `pause_key`, one cron for a `cron_pause_key` — and stores the pause, so it holds across restarts and redeploys. A tick BullMQ already queued is skipped. The box's own sweeps carry no key and cannot be paused." : "Re-arms every recurring job carrying `pause_key` and drops the stored pause. A cron still paused under its other key stays off. BullMQ fires each at its next scheduled time, not immediately.",
|
|
91110
91673
|
request: { body: jsonBody(named("RecurringJobPauseRequest", recurringJobPauseRequestSchema), "The pause key the list reported for the jobs to switch.") },
|
|
91111
91674
|
responses: {
|
|
91112
|
-
200: jsonResponse$
|
|
91675
|
+
200: jsonResponse$2(named("RecurringJobPauseResponse", recurringJobPauseResponseSchema), "The key, its new state, and how many jobs it covered."),
|
|
91113
91676
|
400: errorResponse("The body failed its schema."),
|
|
91114
91677
|
403: forbiddenResponse("config:write"),
|
|
91115
91678
|
404: errorResponse("No recurring job on this box carries that pause key.")
|
|
@@ -91763,7 +92326,7 @@ var createLimitsRouter = (options) => {
|
|
|
91763
92326
|
tags: [LIMITS_TAG],
|
|
91764
92327
|
summary: "List the workspace's concurrency limits",
|
|
91765
92328
|
description: "One item per limit `stackbone.config.ts` declares: the declared capacity, the operator's override, the capacity the box enforces, and how many slots are busy and waiting now. A box with no limits answers an empty list.",
|
|
91766
|
-
responses: { 200: jsonResponse$
|
|
92329
|
+
responses: { 200: jsonResponse$2(named("LimitersResponse", limitersResponseSchema), "Every declared limit, plus the box's own clock.") }
|
|
91767
92330
|
}), async (c) => {
|
|
91768
92331
|
const limits = options.resolveLimits();
|
|
91769
92332
|
const items = limits ? await limits.listLimiters() : [];
|
|
@@ -91780,7 +92343,7 @@ var createLimitsRouter = (options) => {
|
|
|
91780
92343
|
description: "The slots of one limit: held first, then granted, then the queue in arrival order. A granted slot waits for its run to confirm it until `claimDeadline`; a busy slot goes back to the box at `expiresAt`.",
|
|
91781
92344
|
request: { params: namePath },
|
|
91782
92345
|
responses: {
|
|
91783
|
-
200: jsonResponse$
|
|
92346
|
+
200: jsonResponse$2(named("SlotsResponse", slotsResponseSchema), "The limit and its slots."),
|
|
91784
92347
|
400: errorResponse("The name is not a limit name (`invalid_payload`)."),
|
|
91785
92348
|
404: errorResponse("The workspace declares no limit of that name.")
|
|
91786
92349
|
}
|
|
@@ -91805,7 +92368,7 @@ var createLimitsRouter = (options) => {
|
|
|
91805
92368
|
description: "Gives a granted or held slot back and grants the next run in the queue. For a slot whose release was lost: the work ended but nothing called `releaseSlot`. Idempotent: a slot already released, or still waiting, answers `released: false` and changes nothing.",
|
|
91806
92369
|
request: { params: slotPath },
|
|
91807
92370
|
responses: {
|
|
91808
|
-
200: jsonResponse$
|
|
92371
|
+
200: jsonResponse$2(named("ReleaseSlotResponse", releaseSlotResponseSchema), "Whether this call released the slot."),
|
|
91809
92372
|
400: errorResponse("The id is not a uuid (`invalid_payload`)."),
|
|
91810
92373
|
403: forbiddenResponse(RELEASE_CAPABILITY),
|
|
91811
92374
|
503: errorResponse("This box runs no concurrency-limits service.")
|
|
@@ -91843,7 +92406,7 @@ var createLimitsRouter = (options) => {
|
|
|
91843
92406
|
body: jsonBody(named("LimiterUpdateRequest", limiterUpdateRequestSchema), "The new capacity and/or max hold; `null` clears one.")
|
|
91844
92407
|
},
|
|
91845
92408
|
responses: {
|
|
91846
|
-
200: jsonResponse$
|
|
92409
|
+
200: jsonResponse$2(LIMITER, "The limit with its new overrides."),
|
|
91847
92410
|
400: errorResponse("The name or the body failed its schema (`invalid_payload`)."),
|
|
91848
92411
|
403: forbiddenResponse(OVERRIDES_CAPABILITY),
|
|
91849
92412
|
404: errorResponse("The workspace declares no limit of that name."),
|
|
@@ -91897,153 +92460,6 @@ var mountLimits = (app, ctx) => {
|
|
|
91897
92460
|
}));
|
|
91898
92461
|
};
|
|
91899
92462
|
//#endregion
|
|
91900
|
-
//#region ../../libs/runtime/workspace-runtime/src/lib/voice/livekit-credentials.ts
|
|
91901
|
-
init_src$26();
|
|
91902
|
-
var [URL_NAME, API_KEY_NAME, API_SECRET_NAME] = RESERVED_LIVEKIT_SECRET_NAMES;
|
|
91903
|
-
/**
|
|
91904
|
-
* Names a row this box's key cannot open (never its value), so the log says
|
|
91905
|
-
* which one to save again. True when `err` is that failure.
|
|
91906
|
-
*/
|
|
91907
|
-
var warnUndecryptable = (scope, name) => console.warn(`[${scope}] ${name} cannot be opened with this box's key; save it again`);
|
|
91908
|
-
var warnIfUndecryptable = (scope, name, err) => {
|
|
91909
|
-
if (!(err instanceof SecretUndecryptableError)) return false;
|
|
91910
|
-
warnUndecryptable(scope, name);
|
|
91911
|
-
return true;
|
|
91912
|
-
};
|
|
91913
|
-
/**
|
|
91914
|
-
* The stored LiveKit credentials, read on EVERY call — no cache — or `null` when
|
|
91915
|
-
* any of the three is missing or empty. Half a credential set cannot open a
|
|
91916
|
-
* room, so it answers exactly what nothing stored answers.
|
|
91917
|
-
*
|
|
91918
|
-
* A row this box's key cannot open throws {@link SecretUndecryptableError},
|
|
91919
|
-
* after a warning that names the row (never its value): saving the credentials
|
|
91920
|
-
* again is the fix, and the caller tells the operator so. Any other failure
|
|
91921
|
-
* throws as it came.
|
|
91922
|
-
*/
|
|
91923
|
-
var readLiveKitCredentials = async (opts) => {
|
|
91924
|
-
const values = await readSystemSecrets(opts, RESERVED_LIVEKIT_SECRET_NAMES, (name) => warnUndecryptable("voice", name));
|
|
91925
|
-
const url = values.get(URL_NAME);
|
|
91926
|
-
const apiKey = values.get(API_KEY_NAME);
|
|
91927
|
-
const apiSecret = values.get(API_SECRET_NAME);
|
|
91928
|
-
if (!url || !apiKey || !apiSecret) return null;
|
|
91929
|
-
return {
|
|
91930
|
-
url,
|
|
91931
|
-
apiKey,
|
|
91932
|
-
apiSecret
|
|
91933
|
-
};
|
|
91934
|
-
};
|
|
91935
|
-
/** The secret naming the LiveKit outbound SIP trunk `placeCall` dials through (STA3-56). */
|
|
91936
|
-
var SIP_OUTBOUND_TRUNK_SECRET = "LIVEKIT_SIP_OUTBOUND_TRUNK_ID";
|
|
91937
|
-
/** The optional secret naming the number the callee sees; the trunk's own when absent. */
|
|
91938
|
-
var SIP_CALLER_ID_SECRET = "LIVEKIT_SIP_CALLER_ID";
|
|
91939
|
-
/**
|
|
91940
|
-
* The outbound trunk, read on every call, or `null` when no trunk id is saved.
|
|
91941
|
-
* Not reserved names, unlike the three credentials: a trunk id opens nothing on
|
|
91942
|
-
* its own and has no privileged screen yet, so it is read from an `is_system`
|
|
91943
|
-
* row or the operator's own secret of that name, whichever exists.
|
|
91944
|
-
*/
|
|
91945
|
-
var readSipOutboundConfig = async (opts) => {
|
|
91946
|
-
const values = await readSecretsAnyKind(opts, [SIP_OUTBOUND_TRUNK_SECRET, SIP_CALLER_ID_SECRET], (name) => warnUndecryptable("voice", name));
|
|
91947
|
-
const trunkId = values.get(SIP_OUTBOUND_TRUNK_SECRET)?.trim();
|
|
91948
|
-
if (!trunkId) return null;
|
|
91949
|
-
const callerId = values.get(SIP_CALLER_ID_SECRET)?.trim();
|
|
91950
|
-
return {
|
|
91951
|
-
trunkId,
|
|
91952
|
-
...callerId ? { callerId } : {}
|
|
91953
|
-
};
|
|
91954
|
-
};
|
|
91955
|
-
var voiceNotConfigured = (message) => ({ refusal: {
|
|
91956
|
-
status: 503,
|
|
91957
|
-
body: {
|
|
91958
|
-
code: "voice_not_configured",
|
|
91959
|
-
message
|
|
91960
|
-
}
|
|
91961
|
-
} });
|
|
91962
|
-
/**
|
|
91963
|
-
* {@link readLiveKitCredentials} for a voice route: the credentials, or the 503 to
|
|
91964
|
-
* answer without them. A row sealed by another box key is fixed the way a missing one
|
|
91965
|
-
* is, by saving the credentials again, so it answers the same 503; any other read
|
|
91966
|
-
* failure is the box's own and throws, to the generic 500.
|
|
91967
|
-
*/
|
|
91968
|
-
var lookUpLiveKitCredentials = async (opts) => {
|
|
91969
|
-
let credentials;
|
|
91970
|
-
try {
|
|
91971
|
-
credentials = await readLiveKitCredentials(opts);
|
|
91972
|
-
} catch (err) {
|
|
91973
|
-
if (!(err instanceof SecretUndecryptableError)) throw err;
|
|
91974
|
-
return voiceNotConfigured("This box cannot read the saved LiveKit credentials: they were saved with another key. Open the Voice provider screen and save them again.");
|
|
91975
|
-
}
|
|
91976
|
-
if (!credentials) return voiceNotConfigured("This box has no LiveKit credentials. Save the URL, API key and API secret on the Voice provider screen.");
|
|
91977
|
-
return { credentials };
|
|
91978
|
-
};
|
|
91979
|
-
/**
|
|
91980
|
-
* The screen's view: whether all three are saved, and the URL in clear.
|
|
91981
|
-
*
|
|
91982
|
-
* The key and the secret are asked after as EXISTENCE only, never decrypted:
|
|
91983
|
-
* this view never shows them, so opening them would be work done to throw away.
|
|
91984
|
-
* The URL is decrypted to be shown, and a URL row this box's key cannot open
|
|
91985
|
-
* (sealed by another box) reads as not configured instead of failing the screen
|
|
91986
|
-
* the operator needs to replace it from. Only that: a database or key failure
|
|
91987
|
-
* throws, because answering "not configured" would hide a configured box.
|
|
91988
|
-
*/
|
|
91989
|
-
var getVoiceProvider = async (opts) => {
|
|
91990
|
-
const [url, apiKeySet, apiSecretSet] = await Promise.all([
|
|
91991
|
-
readSystemSecret(opts, URL_NAME).catch((err) => {
|
|
91992
|
-
if (!warnIfUndecryptable("voice-provider", URL_NAME, err)) throw err;
|
|
91993
|
-
return null;
|
|
91994
|
-
}),
|
|
91995
|
-
systemSecretExists(opts, API_KEY_NAME),
|
|
91996
|
-
systemSecretExists(opts, API_SECRET_NAME)
|
|
91997
|
-
]);
|
|
91998
|
-
return {
|
|
91999
|
-
configured: url !== null && apiKeySet && apiSecretSet,
|
|
92000
|
-
url
|
|
92001
|
-
};
|
|
92002
|
-
};
|
|
92003
|
-
/**
|
|
92004
|
-
* Save all three as `is_system` rows, with the activity-log entry, in ONE
|
|
92005
|
-
* transaction: the box never holds a new URL beside an old secret, and never a
|
|
92006
|
-
* save the trail does not show. The entry names the URL alone.
|
|
92007
|
-
*/
|
|
92008
|
-
var setVoiceProvider = async (opts, body, actor = { userId: null }) => {
|
|
92009
|
-
await opts.db.transaction(async (tx) => {
|
|
92010
|
-
await seedSystemSecrets(opts, {
|
|
92011
|
-
[URL_NAME]: body.url,
|
|
92012
|
-
[API_KEY_NAME]: body.apiKey,
|
|
92013
|
-
[API_SECRET_NAME]: body.apiSecret
|
|
92014
|
-
}, tx);
|
|
92015
|
-
await opts.audit.record({
|
|
92016
|
-
action: "voice_provider.update",
|
|
92017
|
-
actorUserId: actor.userId,
|
|
92018
|
-
subjectType: "voice_provider",
|
|
92019
|
-
payload: { url: body.url }
|
|
92020
|
-
}, tx);
|
|
92021
|
-
});
|
|
92022
|
-
await notifySecretsChanged();
|
|
92023
|
-
};
|
|
92024
|
-
/**
|
|
92025
|
-
* Remove all three in ONE transaction, and answer the names actually removed.
|
|
92026
|
-
*
|
|
92027
|
-
* `cleared` comes off each DELETE's own answer, so a partial store records only
|
|
92028
|
-
* what it held, and a removal that found nothing records nothing — the rule
|
|
92029
|
-
* `removeModelProvider` keeps.
|
|
92030
|
-
*/
|
|
92031
|
-
var clearVoiceProvider = async (opts, actor = { userId: null }) => {
|
|
92032
|
-
const cleared = await opts.db.transaction(async (tx) => {
|
|
92033
|
-
const removed = [];
|
|
92034
|
-
for (const name of RESERVED_LIVEKIT_SECRET_NAMES) if (await deleteSystemSecret(opts, name, tx)) removed.push(name);
|
|
92035
|
-
if (removed.length > 0) await opts.audit.record({
|
|
92036
|
-
action: "voice_provider.delete",
|
|
92037
|
-
actorUserId: actor.userId,
|
|
92038
|
-
subjectType: "voice_provider",
|
|
92039
|
-
payload: { cleared: removed }
|
|
92040
|
-
}, tx);
|
|
92041
|
-
return removed;
|
|
92042
|
-
});
|
|
92043
|
-
if (cleared.length > 0) await notifySecretsChanged();
|
|
92044
|
-
return cleared;
|
|
92045
|
-
};
|
|
92046
|
-
//#endregion
|
|
92047
92463
|
//#region ../../libs/runtime/workspace-runtime/src/lib/voice/agent-voice-settings.ts
|
|
92048
92464
|
init_src$26();
|
|
92049
92465
|
/**
|
|
@@ -92761,7 +93177,7 @@ var createApiKeysRouter = (options) => {
|
|
|
92761
93177
|
summary: "List the workspace API keys",
|
|
92762
93178
|
description: "Every key this workspace has ever minted, newest first — REVOKED ONES INCLUDED, because a key that vanished from the list cannot answer \"what was that, and who turned it off\". Each row carries the prefix and the selector with the secret masked; the secret itself is not stored and cannot be answered here.",
|
|
92763
93179
|
responses: {
|
|
92764
|
-
200: jsonResponse$
|
|
93180
|
+
200: jsonResponse$2(named("StudioApiKeyListResponse", studioApiKeyListResponseSchema), "Every key this workspace holds, masked."),
|
|
92765
93181
|
403: forbiddenResponse(API_KEYS_CAPABILITY)
|
|
92766
93182
|
}
|
|
92767
93183
|
}), async (c) => c.json(await listApiKeys(options), 200));
|
|
@@ -92775,7 +93191,7 @@ var createApiKeysRouter = (options) => {
|
|
|
92775
93191
|
description: "Answers the full `sbk_live_…` key ONCE, in `key`. The box keeps the selector and a SHA-256 digest of the secret, so this response is the only time the plaintext exists anywhere — a caller that loses it revokes the key and mints another.\n\n`permissions` is fixed at mint: a key's reach cannot be edited afterwards, because widening a credential in place leaves nothing on the trail saying what it used to be allowed to do. `expires_at` may be omitted or null, which means \"valid until revoked\".",
|
|
92776
93192
|
request: { body: jsonBody(named("CreateStudioApiKeyRequest", createStudioApiKeyRequestSchema), "The display name, what the key may call, and when it stops working.") },
|
|
92777
93193
|
responses: {
|
|
92778
|
-
201: jsonResponse$
|
|
93194
|
+
201: jsonResponse$2(named("CreateStudioApiKeyResponse", createStudioApiKeyResponseSchema), "The masked key, and the plaintext — answered here and nowhere else, ever."),
|
|
92779
93195
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
92780
93196
|
403: forbiddenResponse(API_KEYS_CAPABILITY)
|
|
92781
93197
|
}
|
|
@@ -92789,7 +93205,7 @@ var createApiKeysRouter = (options) => {
|
|
|
92789
93205
|
description: "The masked row. There is no route that answers the secret a second time, and no row that could.",
|
|
92790
93206
|
request: { params: looseIdPath },
|
|
92791
93207
|
responses: {
|
|
92792
|
-
200: jsonResponse$
|
|
93208
|
+
200: jsonResponse$2(named("StudioApiKeyResponse", studioApiKeyResponseSchema), "The key, masked."),
|
|
92793
93209
|
403: forbiddenResponse(API_KEYS_CAPABILITY),
|
|
92794
93210
|
404: errorResponse("No such key — or an id that is not an id at all.")
|
|
92795
93211
|
}
|
|
@@ -92813,7 +93229,7 @@ var createApiKeysRouter = (options) => {
|
|
|
92813
93229
|
description: "Stops the key working, immediately and for good. A SOFT transition: the row stays and is stamped with when it happened and who did it, so the list can still explain a key that used to work. There is no un-revoke — mint another.",
|
|
92814
93230
|
request: { params: looseIdPath },
|
|
92815
93231
|
responses: {
|
|
92816
|
-
200: jsonResponse$
|
|
93232
|
+
200: jsonResponse$2(named("StudioApiKeyResponse", studioApiKeyResponseSchema), "The key as it now stands, revoked."),
|
|
92817
93233
|
403: forbiddenResponse(API_KEYS_CAPABILITY),
|
|
92818
93234
|
404: errorResponse("No such key — or an id that is not an id at all."),
|
|
92819
93235
|
409: errorResponse("The key was already revoked (`api_key_already_revoked`). The first revocation stands: re-stamping it would move the moment the key died and rename whoever stopped it.")
|
|
@@ -92908,7 +93324,7 @@ var createSecretsRouter = (secretsOptions) => {
|
|
|
92908
93324
|
summary: "List the workspace secrets",
|
|
92909
93325
|
description: "The names, descriptions and rotation timestamps. Values never leave through this route — each row carries a masked placeholder, and the plaintext is only ever answered by the reveal below.",
|
|
92910
93326
|
responses: {
|
|
92911
|
-
200: jsonResponse$
|
|
93327
|
+
200: jsonResponse$2(named("StudioSecretListResponse", studioSecretListResponseSchema), "Every secret this workspace holds, masked."),
|
|
92912
93328
|
403: forbiddenResponse("secrets:rotate")
|
|
92913
93329
|
}
|
|
92914
93330
|
}), async (c) => c.json(await listSecrets$1(secretsOptions), 200));
|
|
@@ -92922,7 +93338,7 @@ var createSecretsRouter = (secretsOptions) => {
|
|
|
92922
93338
|
description: "Writes the value under `name`, replacing whatever was there. The reply carries the masked row, never the value that was just written.",
|
|
92923
93339
|
request: { body: jsonBody(named("CreateStudioSecretRequest", createStudioSecretRequestSchema), "The name, the value and an optional description.") },
|
|
92924
93340
|
responses: {
|
|
92925
|
-
200: jsonResponse$
|
|
93341
|
+
200: jsonResponse$2(named("StudioSecretMutationResponse", studioSecretMutationResponseSchema), "The secret as it now stands, masked."),
|
|
92926
93342
|
400: errorResponse("The body failed its schema (`invalid_payload`), or the name is one the install owns (`studio_secret_system_name`)."),
|
|
92927
93343
|
403: forbiddenResponse("secrets:rotate")
|
|
92928
93344
|
}
|
|
@@ -92947,7 +93363,7 @@ var createSecretsRouter = (secretsOptions) => {
|
|
|
92947
93363
|
description: "Removes the row and reloads the log scrubber before answering, so the value stops being masked only once it is gone.",
|
|
92948
93364
|
request: { params: looseSecretNamePath },
|
|
92949
93365
|
responses: {
|
|
92950
|
-
200: jsonResponse$
|
|
93366
|
+
200: jsonResponse$2(named("StudioSecretDeleteResponse", studioSecretDeleteResponseSchema), "The secret is gone."),
|
|
92951
93367
|
400: errorResponse("The name is one the install owns (`studio_secret_system_name`)."),
|
|
92952
93368
|
403: forbiddenResponse("secrets:rotate"),
|
|
92953
93369
|
404: errorResponse("No such secret — or a name that is not a name at all.")
|
|
@@ -92980,7 +93396,7 @@ var createSecretsRouter = (secretsOptions) => {
|
|
|
92980
93396
|
description: "The only route that answers the plaintext, and the only one gated on `secrets:reveal` (owner/admin). The local box skips the re-auth ceremony the cloud path runs — a single-developer machine has nobody to challenge. Every answered reveal is recorded on the activity log, naming the secret and whoever asked.",
|
|
92981
93397
|
request: { params: looseSecretNamePath },
|
|
92982
93398
|
responses: {
|
|
92983
|
-
200: jsonResponse$
|
|
93399
|
+
200: jsonResponse$2(named("StudioSecretRevealResponse", studioSecretRevealResponseSchema), "The plaintext, and when the reveal stops being valid."),
|
|
92984
93400
|
403: forbiddenResponse("secrets:reveal"),
|
|
92985
93401
|
404: errorResponse("No such secret — or a name that is not a name at all.")
|
|
92986
93402
|
}
|
|
@@ -93128,7 +93544,7 @@ var createSessionsRouter = (deps) => {
|
|
|
93128
93544
|
description: "Cursor-paginated, newest first, filterable by agent, status and one `key:value` run tag (`tag`: the sessions with at least one run carrying it); `system=exclude` leaves out the sessions of the agents the platform installed for itself. Each row carries the read-time aggregates (turn count, last activity, preview, token totals). A workspace with no sessions store wired — a classic project — reports an empty page rather than an error.",
|
|
93129
93545
|
request: { query: listSessionsQuerySchema },
|
|
93130
93546
|
responses: {
|
|
93131
|
-
200: jsonResponse$
|
|
93547
|
+
200: jsonResponse$2(named("SessionListResponse", sessionListResponseSchema), "One page of sessions, and the cursors either side of it."),
|
|
93132
93548
|
400: errorResponse("The query failed its schema (`invalid_payload`), or the cursor is not one this box issued (`invalid_cursor`).")
|
|
93133
93549
|
}
|
|
93134
93550
|
}), async (c) => {
|
|
@@ -93154,7 +93570,7 @@ var createSessionsRouter = (deps) => {
|
|
|
93154
93570
|
description: "The lifecycle row plus the same read-time aggregates the list carries. Not the turns — those are runs, read through `/api/runs`.",
|
|
93155
93571
|
request: { params: looseSessionIdPath },
|
|
93156
93572
|
responses: {
|
|
93157
|
-
200: jsonResponse$
|
|
93573
|
+
200: jsonResponse$2(named("Session", sessionSchema), "The session as it now stands."),
|
|
93158
93574
|
404: errorResponse("No such session — or an id that is not an id at all.")
|
|
93159
93575
|
}
|
|
93160
93576
|
}), async (c) => {
|
|
@@ -93323,7 +93739,7 @@ var createSettingsRouter = (opts, hooks = {}) => {
|
|
|
93323
93739
|
tags: [SETTINGS_TAG],
|
|
93324
93740
|
summary: "Read the workspace settings",
|
|
93325
93741
|
description: "Every knob, with the documented default filled in for anything nobody has written — a workspace that was never configured reads defaults, never an empty object. `updatedAt`/`updatedBy` are null until the first save, and `updatedByEmail` names only the self-host principal — every other author is an id for the caller to resolve against its member directory.",
|
|
93326
|
-
responses: { 200: jsonResponse$
|
|
93742
|
+
responses: { 200: jsonResponse$2(named("WorkspaceSettingsResponse", workspaceSettingsResponseSchema), "The resolved settings, and who last changed them.") }
|
|
93327
93743
|
}), async (c) => c.json(await getWorkspaceSettings(opts), 200));
|
|
93328
93744
|
app.openapi(createRoute({
|
|
93329
93745
|
method: "put",
|
|
@@ -93335,7 +93751,7 @@ var createSettingsRouter = (opts, hooks = {}) => {
|
|
|
93335
93751
|
description: "A SHALLOW merge: an absent key leaves the stored value alone, an explicit `null` clears it back to unset. The reply echoes the fresh view so the screen reflects the merged result without a second round-trip. Some keys carry a SECOND, per-key gate on top of `config:write` — see the 403.",
|
|
93336
93752
|
request: { body: jsonBody(named("WorkspaceSettingsPatch", workspaceSettingsPatchSchema), "The keys to change. Unknown keys are refused: an unrecognised key is almost always a typo, and accepting it would look like a save that changed nothing.") },
|
|
93337
93753
|
responses: {
|
|
93338
|
-
200: jsonResponse$
|
|
93754
|
+
200: jsonResponse$2(named("WorkspaceSettingsResponse", workspaceSettingsResponseSchema), "The settings as they now stand, with this call stamped as the author."),
|
|
93339
93755
|
400: errorResponse("The patch failed its schema (`invalid_payload`)."),
|
|
93340
93756
|
403: errorResponse("The caller does not hold `config:write` — or the patch names a key that needs a SECOND capability on top of it (`forbidden`): `logRetentionDays` / `logStorageBudgetGb` / `eventRetentionDays` need `retention:write`, `mcpEnabled` needs `mcp:manage`, `copilotEnabled` needs `copilot:manage`. Those govern how long the box keeps its own evidence, whether it answers MCP at all and whether Studio shows its copilot to the workspace, so the coarser capability is not enough. The WHOLE patch is refused, never half of it.")
|
|
93341
93757
|
}
|
|
@@ -93434,7 +93850,7 @@ var createStorageRouter = (deps) => {
|
|
|
93434
93850
|
tags: [STORAGE_TAG],
|
|
93435
93851
|
summary: "List the buckets this box can browse",
|
|
93436
93852
|
description: "Every bucket the box's S3 credential reaches — one under `stackbone dev` (the docker-compose MinIO), the install's own under a deployed image.",
|
|
93437
|
-
responses: { 200: jsonResponse$
|
|
93853
|
+
responses: { 200: jsonResponse$2(named("StudioStorageBucketListResponse", studioStorageBucketListResponseSchema), "The buckets, each with the kind of store behind it.") }
|
|
93438
93854
|
}), async (c) => c.json(await listBuckets$1(deps.storage), 200));
|
|
93439
93855
|
app.openapi(createRoute({
|
|
93440
93856
|
method: "get",
|
|
@@ -93447,7 +93863,7 @@ var createStorageRouter = (deps) => {
|
|
|
93447
93863
|
query: listStudioStorageObjectsQuerySchema
|
|
93448
93864
|
},
|
|
93449
93865
|
responses: {
|
|
93450
|
-
200: jsonResponse$
|
|
93866
|
+
200: jsonResponse$2(named("StudioStorageObjectListResponse", studioStorageObjectListResponseSchema), "One page of objects, the folders below the prefix, and the cursor for the next page."),
|
|
93451
93867
|
400: errorResponse("The query failed its schema (`invalid_payload`)."),
|
|
93452
93868
|
404: errorResponse("No such bucket (`bucket_not_found`).")
|
|
93453
93869
|
}
|
|
@@ -93469,7 +93885,7 @@ var createStorageRouter = (deps) => {
|
|
|
93469
93885
|
body: multipartBody(named("StudioStorageUploadForm", uploadFormSchema), "The object bytes and the key to write them under.")
|
|
93470
93886
|
},
|
|
93471
93887
|
responses: {
|
|
93472
|
-
200: jsonResponse$
|
|
93888
|
+
200: jsonResponse$2(named("StudioStorageUploadResponse", studioStorageUploadResponseSchema), "The stored key, its size and the etag the store minted."),
|
|
93473
93889
|
400: errorResponse("The body is not `multipart/form-data`, or it is missing `file` or `key` — or sent `file` as text rather than a part (`invalid_payload`)."),
|
|
93474
93890
|
403: errorResponse("The caller's role does not grant `runs:invoke` — or the key is under `rag/`, which is reserved for managed RAG originals and cannot be written from the explorer (`rag_storage_prefix_reserved`)."),
|
|
93475
93891
|
404: errorResponse("No such bucket (`bucket_not_found`), or a key this box cannot write (`object_not_found`).")
|
|
@@ -93512,7 +93928,7 @@ var createStorageRouter = (deps) => {
|
|
|
93512
93928
|
body: jsonBody(named("CreateStudioStorageFolderRequest", createStudioStorageFolderRequestSchema), "The full folder path, composed from the breadcrumb and the name the user typed.")
|
|
93513
93929
|
},
|
|
93514
93930
|
responses: {
|
|
93515
|
-
200: jsonResponse$
|
|
93931
|
+
200: jsonResponse$2(named("StudioStorageCreateFolderResponse", studioStorageCreateFolderResponseSchema), "The normalised prefix that was created, always ending in `/`."),
|
|
93516
93932
|
400: errorResponse("The body failed its schema, or the folder name is one the store will not take — `..` traversal and the like (`invalid_folder`)."),
|
|
93517
93933
|
403: errorResponse("The caller's role does not grant `runs:invoke` — or the prefix is `rag/`, which is reserved for managed RAG originals and cannot be written from the explorer (`rag_storage_prefix_reserved`)."),
|
|
93518
93934
|
404: errorResponse("No such bucket (`bucket_not_found`).")
|
|
@@ -93592,7 +94008,7 @@ var createStorageRouter = (deps) => {
|
|
|
93592
94008
|
body: jsonBody(named("StudioStoragePresignRequest", studioStoragePresignRequestSchema), "The key to sign for, and how long the URL should live.")
|
|
93593
94009
|
},
|
|
93594
94010
|
responses: {
|
|
93595
|
-
200: jsonResponse$
|
|
94011
|
+
200: jsonResponse$2(named("StudioStoragePresignResponse", studioStoragePresignResponseSchema), "The signed URL and the moment it stops working."),
|
|
93596
94012
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
93597
94013
|
404: errorResponse("No such bucket (`bucket_not_found`).")
|
|
93598
94014
|
}
|
|
@@ -93941,7 +94357,7 @@ var createVoiceProviderRouter = (secretsOptions, options = {}) => {
|
|
|
93941
94357
|
summary: "Read the LiveKit credentials status",
|
|
93942
94358
|
description: "`configured` is true only when the URL, the API key and the API secret are all saved. The URL rides back in clear; the key and the secret never do, not even masked. A partial store reads as not configured, with whatever URL it holds.",
|
|
93943
94359
|
responses: {
|
|
93944
|
-
200: jsonResponse$
|
|
94360
|
+
200: jsonResponse$2(voiceProviderGetResponse, "Whether voice is configured, and the URL."),
|
|
93945
94361
|
403: forbiddenResponse("secrets:rotate")
|
|
93946
94362
|
}
|
|
93947
94363
|
}), async (c) => c.json(await getVoiceProvider(secretsOptions), 200));
|
|
@@ -93955,7 +94371,7 @@ var createVoiceProviderRouter = (secretsOptions, options = {}) => {
|
|
|
93955
94371
|
description: "Writes the URL, the API key and the API secret together, in one transaction with the activity-log entry: all three land or none does. The URL must be `wss://` or `https://`, and the key and secret non-empty. The reply is the fresh view, which never carries the key or the secret.",
|
|
93956
94372
|
request: { body: jsonBody(named("VoiceProviderPutRequest", voiceProviderPutRequestSchema), "The LiveKit URL, API key and API secret.") },
|
|
93957
94373
|
responses: {
|
|
93958
|
-
200: jsonResponse$
|
|
94374
|
+
200: jsonResponse$2(voiceProviderGetResponse, "The fresh view after the save."),
|
|
93959
94375
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
93960
94376
|
403: forbiddenResponse("secrets:rotate")
|
|
93961
94377
|
}
|
|
@@ -93977,7 +94393,7 @@ var createVoiceProviderRouter = (secretsOptions, options = {}) => {
|
|
|
93977
94393
|
summary: "Remove the LiveKit credentials",
|
|
93978
94394
|
description: "Deletes the URL, the API key and the API secret in one transaction. Removing nothing is not an error: the reply is the view afterwards either way, and only a removal that deleted a row is recorded on the activity log.",
|
|
93979
94395
|
responses: {
|
|
93980
|
-
200: jsonResponse$
|
|
94396
|
+
200: jsonResponse$2(voiceProviderGetResponse, "The view after the removal."),
|
|
93981
94397
|
403: forbiddenResponse("secrets:rotate")
|
|
93982
94398
|
}
|
|
93983
94399
|
}), async (c) => {
|
|
@@ -94008,7 +94424,7 @@ var createVoiceRecordingsRouter = (options) => {
|
|
|
94008
94424
|
description: "The latest call recording of a session, from the box bucket the room route's egress uploads to (an audio-only OGG under `voice/recordings/<session_id>/<room>.ogg`). `status` is `recording` until LiveKit reports the egress ended, then `ready` or `failed`; the read asks LiveKit, so a call that just ended reads `ready` on the next read. Only a `ready` recording carries `url`, a presigned read valid one hour and unaudited (D32), and `expires_at`.",
|
|
94009
94425
|
request: { query: voiceRecordingQuerySchema },
|
|
94010
94426
|
responses: {
|
|
94011
|
-
200: jsonResponse$
|
|
94427
|
+
200: jsonResponse$2(named("VoiceRecordingResponse", voiceRecordingResponseSchema), "The recording, with a presigned read once it is ready."),
|
|
94012
94428
|
400: errorResponse("`session_id` is not a UUID (`invalid_payload`)."),
|
|
94013
94429
|
403: forbiddenResponse(VOICE_CAPABILITY$2),
|
|
94014
94430
|
404: errorResponse("The session has no call recording (`recording_not_found`).")
|
|
@@ -94354,7 +94770,7 @@ var createVoiceRoomsRouter = (options) => {
|
|
|
94354
94770
|
description: "Opens a fresh room `call-<agent>-<8 hex>` and answers the LiveKit URL, the browser's token to join it (it joins within five minutes; a call already running is not cut) and the settings the call runs with. The token carries the room's agent dispatch: joining the room sends the voice worker in with the agent's name, its effective voice settings, the session key `voice-<room>` every turn lands in, the tags each turn's run carries (`channel: voice`, `room`, and `origin`, which the caller states and which defaults to `studio`), the box's public URL the worker posts its turns to, and a call token: the worker's bearer for this call, valid two hours, until the box restarts, on the AG-UI chat of this agent and this session key on the trace relay `POST /voice/v1/traces`, and on this call's identity bootstrap, knowledge prefetch and heard completion at `/voice/v1/calls/{sessionKey}/bootstrap`, `/voice/v1/calls/{sessionKey}/knowledge` and `/voice/v1/calls/{sessionKey}/complete`, nowhere else. The box freezes memory policy, the agent's knowledge settings (`rag`), its refusal phrase (`refusalPhrase`, what the caller hears when a guardrail stops their sentence or the agent's reply) and the zone its turns are told today's date in (`timeZone`), none of them in the dispatch, at room acceptance, so a change saved in Studio reaches the next call; the worker supplies the original SIP participant's phoneNumber before output, or the browser call uses explicit identities supplied on this request. `variables` fill the `{{name}}` placeholders of the agent's greeting before it enters the dispatch (a missing one is left out) and reach every model call of the call as data. The bootstrap response contains no recalled facts or policy. On a box that exports traces (`OTEL_EXPORTER_OTLP_*`), the dispatch also asks the worker to send the call's spans to that relay with that token. The call is recorded to the box bucket as audio-only OGG only when the agent's `recording` voice setting is on (it is off by default) and the box bucket is not on a loopback or private host; a recording that fails never fails the call. The LiveKit API key and secret are never answered. The call token is answered only inside the room token's dispatch, which anyone holding the room token can read.",
|
|
94355
94771
|
request: { body: jsonBody(named("VoiceRoomRequest", voiceRoomRequestSchema), "The agent to call.") },
|
|
94356
94772
|
responses: {
|
|
94357
|
-
200: jsonResponse$
|
|
94773
|
+
200: jsonResponse$2(named("VoiceRoomResponse", voiceRoomResponseSchema), "The room, its participant token, and what the dispatch carries."),
|
|
94358
94774
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
94359
94775
|
403: forbiddenResponse(VOICE_CAPABILITY$1),
|
|
94360
94776
|
...VOICE_AGENT_REFUSALS,
|
|
@@ -94458,7 +94874,7 @@ var createVoiceSettingsRouter = (options) => {
|
|
|
94458
94874
|
description: "The settings the agent's next call runs with: the fields saved in Studio over the agent's code over the box defaults, the layer each leaf came from (`studio`, `code` or `default`), and `issue`, why a call cannot carry the merged value (`path: message`), or `null`. A saved field can stop combining with the code after a later deploy; the read describes it rather than failing.",
|
|
94459
94875
|
request: { params: agentPath },
|
|
94460
94876
|
responses: {
|
|
94461
|
-
200: jsonResponse$
|
|
94877
|
+
200: jsonResponse$2(SETTINGS_RESPONSE, "The effective settings and their origins."),
|
|
94462
94878
|
400: errorResponse("The name is not a deep agent name (`invalid_payload`)."),
|
|
94463
94879
|
...VOICE_AGENT_REFUSALS
|
|
94464
94880
|
}
|
|
@@ -94480,7 +94896,7 @@ var createVoiceSettingsRouter = (options) => {
|
|
|
94480
94896
|
body: jsonBody(named("StudioVoiceSettingsPatch", studioVoiceSettingsPatchSchema), "The fields to save.")
|
|
94481
94897
|
},
|
|
94482
94898
|
responses: {
|
|
94483
|
-
200: jsonResponse$
|
|
94899
|
+
200: jsonResponse$2(SETTINGS_RESPONSE, "The effective settings once saved."),
|
|
94484
94900
|
400: errorResponse("The body failed its schema (`invalid_payload`), or the saved fields merged over the agent's code and the box defaults break a cross-field rule (`voice_settings_invalid`, the message names the field)."),
|
|
94485
94901
|
403: forbiddenResponse(WRITE_CAPABILITY),
|
|
94486
94902
|
...VOICE_AGENT_REFUSALS
|
|
@@ -94528,7 +94944,7 @@ var createVoiceSettingsRouter = (options) => {
|
|
|
94528
94944
|
query: resetQuery
|
|
94529
94945
|
},
|
|
94530
94946
|
responses: {
|
|
94531
|
-
200: jsonResponse$
|
|
94947
|
+
200: jsonResponse$2(SETTINGS_RESPONSE, "The effective settings once reset."),
|
|
94532
94948
|
400: errorResponse("The name is not a deep agent name, or `fields` names something other than a leaf path (`invalid_payload`), or the fields Studio keeps merged over the agent's code and the box defaults break a cross-field rule (`voice_settings_invalid`, the message names the field)."),
|
|
94533
94949
|
403: forbiddenResponse(WRITE_CAPABILITY),
|
|
94534
94950
|
...VOICE_AGENT_REFUSALS
|
|
@@ -94641,7 +95057,7 @@ var createVoiceStatusRouter = (options) => {
|
|
|
94641
95057
|
summary: "Check whether the box reaches LiveKit",
|
|
94642
95058
|
description: "Lists the LiveKit project's rooms with the saved URL, API key and API secret, and answers whether that worked: `reachable: false` covers a refused key, a host that cannot be reached and a check that ran past five seconds alike, and LiveKit's own error is never answered. The answer is kept ten seconds for the same credentials, so a check repeated inside that window answers the first one's result and its `checkedAt`; saving other credentials checks again on the next read. It never says whether a voice worker is registered: no LiveKit API lists them.",
|
|
94643
95059
|
responses: {
|
|
94644
|
-
200: jsonResponse$
|
|
95060
|
+
200: jsonResponse$2(named("VoiceStatusResponse", voiceStatusResponseSchema), "Whether the box reached LiveKit with its saved credentials."),
|
|
94645
95061
|
403: forbiddenResponse(VOICE_CAPABILITY),
|
|
94646
95062
|
503: errorResponse("The LiveKit credentials are not saved on this box, or a saved one cannot be opened with this box's key and must be saved again (`voice_not_configured`).")
|
|
94647
95063
|
}
|
|
@@ -94811,7 +95227,7 @@ var createVoiceCallsRouter = (options) => {
|
|
|
94811
95227
|
return c.json(errorEnvelope("voice_lifecycle_failed", "The voice lifecycle operation failed."), 500);
|
|
94812
95228
|
});
|
|
94813
95229
|
const callResponses = {
|
|
94814
|
-
200: jsonResponse$
|
|
95230
|
+
200: jsonResponse$2(named("VoiceCallLifecycleResponse", voiceCallLifecycleResponseSchema), "The call operation completed."),
|
|
94815
95231
|
400: errorResponse("Invalid bounded call payload."),
|
|
94816
95232
|
401: errorResponse("Missing, invalid or expired call token."),
|
|
94817
95233
|
403: errorResponse("Only the call token for this session is admitted."),
|
|
@@ -100288,7 +100704,7 @@ var init_trigger_capabilities_handlers = __esmMin((() => {
|
|
|
100288
100704
|
tags: [TRIGGER_CAPABILITIES_TAG],
|
|
100289
100705
|
summary: "List the triggers this box can link a workflow to",
|
|
100290
100706
|
description: "Every RESOLVABLE trigger across the connector registry, flattened. A descriptor whose poll cannot be resolved — a `strategy` with no registered code — is omitted, so the picker never offers a broken link source. A POLLING or WEBHOOK trigger of a piece connector is listed off its document, its props as `scopeParams`; arming a link on one runs the piece's own `onEnable`, which is where a bundle that cannot load or a credential the provider refuses is reported. A WEBHOOK one is never polled: arming it registers the link's own `/api/hooks/{subscriptionId}/{token}` URL at the provider, so it arms only on a box that knows its public URL.\n\nPresent only on a box with an inbound-trigger intake armed. A box that runs deep agents alone does not serve this route and does not describe it.",
|
|
100291
|
-
responses: { 200: jsonResponse$
|
|
100707
|
+
responses: { 200: jsonResponse$2(triggerCapabilitiesResponseSchema, "The link sources, one row per connector trigger.") }
|
|
100292
100708
|
}), (c) => c.json({ items: listTriggerCapabilities(registry) }, 200));
|
|
100293
100709
|
};
|
|
100294
100710
|
}));
|
|
@@ -101199,7 +101615,7 @@ var init_intake_runtime = __esmMin((() => {
|
|
|
101199
101615
|
}));
|
|
101200
101616
|
//#endregion
|
|
101201
101617
|
//#region ../../libs/runtime/workspace-runtime/src/lib/connect-inbound/subscriptions-router.ts
|
|
101202
|
-
var TRIGGER_SUBSCRIPTIONS_TAG, looseSubscriptionIdPath, deleteTriggerSubscriptionQuery, mappingCompileError, KEY_REFUSALS, keyCompileRefusal, keyCompileError, keysCompileError, previewKey, editingWaitOrKey, patchKeysRefusal, linkSampleEvent, namedLink, namedLinkSampleEvent, previewSample, armingMappingRefusal, trimmedScope, VERIFY_TOKEN_PARAM, withGeneratedVerifyToken, scopeValuesRefusal, sameScope, unreachableConnectorRefusal, fencedOperationRefusal, armingCursorSeed, webhookArm, armedWebhookUrl, answerEnableRefusal, armingEnable, transientRefusal, ENABLE_RETRYABLE_RESPONSE, disarmingHook, linkNames, createTriggerSubscriptionsRouter;
|
|
101618
|
+
var TRIGGER_SUBSCRIPTIONS_TAG, looseSubscriptionIdPath, deleteTriggerSubscriptionQuery, mappingCompileError, KEY_REFUSALS, keyCompileRefusal, keyCompileError, keysCompileError, compileKey, targetSchemaRefusal, previewKey, editingWaitOrKey, patchKeysRefusal, linkSampleEvent, namedLink, namedLinkSampleEvent, previewSample, armingMappingRefusal, trimmedScope, VERIFY_TOKEN_PARAM, withGeneratedVerifyToken, scopeValuesRefusal, sameScope, unreachableConnectorRefusal, fencedOperationRefusal, armingCursorSeed, webhookArm, armedWebhookUrl, answerEnableRefusal, armingEnable, transientRefusal, ENABLE_RETRYABLE_RESPONSE, disarmingHook, linkNames, createTriggerSubscriptionsRouter;
|
|
101203
101619
|
var init_subscriptions_router = __esmMin((() => {
|
|
101204
101620
|
init_src$7();
|
|
101205
101621
|
init_src$6();
|
|
@@ -101250,19 +101666,33 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101250
101666
|
return compiled.ok ? null : keyCompileRefusal(key, compiled.error);
|
|
101251
101667
|
};
|
|
101252
101668
|
keysCompileError = (keys) => keyCompileError(keys.correlationKey, "correlationKey") ?? keyCompileError(keys.conversationKey, "conversationKey");
|
|
101253
|
-
|
|
101669
|
+
compileKey = (expression, key) => {
|
|
101254
101670
|
if (!expression?.trim()) return {
|
|
101255
101671
|
ok: true,
|
|
101256
|
-
value:
|
|
101672
|
+
value: null
|
|
101257
101673
|
};
|
|
101258
101674
|
const compiled = compileMapping(expression);
|
|
101259
|
-
if (
|
|
101675
|
+
if (compiled.ok) return compiled;
|
|
101676
|
+
return {
|
|
101260
101677
|
ok: false,
|
|
101261
101678
|
refusal: {
|
|
101262
101679
|
...keyCompileRefusal(key, compiled.error),
|
|
101263
101680
|
error: compiled.error
|
|
101264
101681
|
}
|
|
101265
101682
|
};
|
|
101683
|
+
};
|
|
101684
|
+
targetSchemaRefusal = (validated) => !validated.ok && validated.error.kind !== "schema_validation_error" ? {
|
|
101685
|
+
code: validated.error.kind,
|
|
101686
|
+
message: validated.error.message,
|
|
101687
|
+
error: validated.error
|
|
101688
|
+
} : null;
|
|
101689
|
+
previewKey = async (expression, key, sampleData) => {
|
|
101690
|
+
const compiled = compileKey(expression, key);
|
|
101691
|
+
if (!compiled.ok) return compiled;
|
|
101692
|
+
if (compiled.value === null) return {
|
|
101693
|
+
ok: true,
|
|
101694
|
+
value: {}
|
|
101695
|
+
};
|
|
101266
101696
|
const applied = await applyMapping(compiled.value, sampleData);
|
|
101267
101697
|
if (!applied.ok) {
|
|
101268
101698
|
const { label, failed } = KEY_REFUSALS[key];
|
|
@@ -101297,6 +101727,10 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101297
101727
|
event: own,
|
|
101298
101728
|
source: "link"
|
|
101299
101729
|
};
|
|
101730
|
+
if (isWebhookTrigger(deps.registry, request.connector, request.trigger)) return {
|
|
101731
|
+
event: void 0,
|
|
101732
|
+
source: "none"
|
|
101733
|
+
};
|
|
101300
101734
|
if (deps.fetchSampleEvent && link !== null && link.connector === request.connector && link.trigger === request.trigger) {
|
|
101301
101735
|
const warn = deps.logger?.warn ?? ((message) => console.warn(message));
|
|
101302
101736
|
let fetched = null;
|
|
@@ -101501,7 +101935,7 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101501
101935
|
description: "Arms a new link and, when it is enabled, its polling repeatable. A link on a piece's webhook trigger arms no repeatable: its `onEnable` registers the link's own `/api/hooks/{subscriptionId}/{token}` URL at the provider, under a token minted for this arm and never returned. Every way a link can be born broken is refused here rather than at tick time: a trigger the registry does not resolve, a trigger missing one of the per-subscription values it declares (a mailbox and a Drive folder are both one of these) or carrying one that does not match the shape the param declares, and a `mapping`, `correlationKey` or `conversationKey` that does not compile as JSONata. A link carries the one key its kind reads: a workflow link resumes a run by `correlationKey`, an agent link continues a conversation by `conversationKey`, and the other kind's key is refused (`invalid_payload`) unless it is blank (null, empty or whitespace). A body that names no `targetKind` is a workflow link here too. An agent link with a `conversationKey` may also carry `debounceMs`, from 1 to 10000 ms: how long it waits after an event for more events of the same conversation, so a burst of chat messages gets one answer. Null or absent answers each event as it arrives. A non-null `debounceMs` is refused (`invalid_payload`) on a workflow link and on an agent link whose `conversationKey` is absent or blank. A link being ARMED is held to more: its `mapping` is run over the trigger's sample event and the result validated against the destination's input schema, so a mapping that could only ever be rejected never arms a poll that would consume events and start nothing. A blank or `null` `mapping` is judged on what the link delivers without one: a workflow link gets the event unchanged; an agent link gets it unchanged when it carries a non-empty text `message`, and otherwise as `{ message }` holding the event as text (a string as is, anything else as JSON), so an agent link with no mapping is not refused for an event whose `message` is missing or not text. Nothing is stored in its place: the stored `mapping` stays `null`. A trigger that declares no sample, or an empty one (`{}`), gives that check nothing to judge, and the link arms; once it has received an event, that event is the sample the check reads on every later patch. A link born with `enabled: false` skips that check — it is made on the patch that turns it on.",
|
|
101502
101936
|
request: { body: jsonBody(named("RegisterTriggerSubscriptionRequest", registerTriggerSubscriptionRequestSchema), "The capability to poll, the workflow or agent to start, and the optional JSONata in between. `principalKey` defaults to `app` — the integration's own credential rather than a user grant.") },
|
|
101503
101937
|
responses: {
|
|
101504
|
-
201: jsonResponse$
|
|
101938
|
+
201: jsonResponse$2(named("TriggerSubscriptionResponse", triggerSubscriptionResponseSchema), "The link as it was stored. Its repeatable is armed when `enabled`."),
|
|
101505
101939
|
403: forbiddenResponse("config:write"),
|
|
101506
101940
|
400: errorResponse("The body failed its schema (`invalid_payload`, including a key the link's kind does not read and a `debounceMs` on a workflow link or on an agent link with no `conversationKey`), or the link would be born broken: `unknown_capability` (no such polling trigger on that integration, including when the integration itself is unknown), `scope_required` (a value the trigger declares in `scopeParams` is missing — the message names which, and a mailbox-scoped trigger reports its mailbox this way too), `invalid_scope` (a value that does not match the pattern its param declares), `invalid_mapping`, `invalid_correlation_key` or `invalid_conversation_key` (the JSONata does not compile). A link being ARMED is also refused when its mapping (or, with none, the default its kind delivers) compiles but cannot fill the destination: `mapping_evaluation_failed` (it throws on the trigger's sample event) or `mapping_cannot_produce_input` (the mapped sample violates the destination's input schema — `details.mappingIssues` names the offending paths), and a piece trigger whose own `onEnable` fails: `trigger_enable_failed`, with the piece's message, or the broker's own code when the link's credential could not be minted (`no_token`, `user_authorization_required`, `invalid_grant`, `credential_error`). A piece's webhook link with no event yet is not judged on its mapping: its trigger's declared sample is another account's example, so the check waits for the link's first event. A piece's webhook trigger is refused `webhook_url_unavailable` on a box that knows no public URL its provider could deliver to (`stackbone dev` with no tunnel). Nothing is stored and nothing is armed."),
|
|
101507
101941
|
503: ENABLE_RETRYABLE_RESPONSE
|
|
@@ -101638,11 +102072,11 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101638
102072
|
path: "/preview",
|
|
101639
102073
|
tags: [TRIGGER_SUBSCRIPTIONS_TAG],
|
|
101640
102074
|
summary: "Dry-run a candidate mapping over the trigger sample event",
|
|
101641
|
-
description: "Compiles the JSONata, evaluates it over the sample event, then validates the result against the destination input JSON Schema: the workflow's declared schema, or an agent's required `message` string with at least one character. Whitespace is valid. No mapping is written, no repeatable armed, no delivery journalled and no cursor moved, so it is the safe way to author a mapping before saving one.\n\nThe sample event is the trigger descriptor's own, unless `subscriptionId` names a link on the same trigger. A link that has received an event is evaluated over its first real event (`sampleSource: link`). A link with none yet is evaluated over the newest event its provider holds, read once without claiming or delivering it; that event is kept as the link's sample and echoed back as `sampleEvent` (`sampleSource: fetched`). When that read finds nothing or fails (an empty mailbox, an expired credential, a timeout), the answer is over the descriptor's sample (`sampleSource: declared`), never an error.\n\nA blank or `null` `mapping` maps the way the link delivers: on a workflow it is the identity mapping and the sample event comes back unchanged; on an agent the sample event comes back unchanged when it carries a non-empty text `message`, and otherwise as `{ message }` holding the event as text (a string as is, anything else as JSON). `output` is whatever the expression produced, which may be a scalar or an array as easily as an object.\n\nThe key the link's kind reads is evaluated over the same sample event: `correlationKey` on a workflow link (the run a real event would resume), `conversationKey` on an agent link (the conversation it would land in). As on register, a non-blank key of the other kind is refused (`invalid_payload`); a blank one (null, empty or whitespace) is no key.\n\nPresent only on a box with an inbound-trigger intake armed.",
|
|
102075
|
+
description: "Compiles the JSONata, evaluates it over the sample event, then validates the result against the destination input JSON Schema: the workflow's declared schema, or an agent's required `message` string with at least one character. Whitespace is valid. No mapping is written, no repeatable armed, no delivery journalled and no cursor moved, so it is the safe way to author a mapping before saving one.\n\nThe sample event is the trigger descriptor's own, unless `subscriptionId` names a link on the same trigger. A link that has received an event is evaluated over its first real event (`sampleSource: link`). A link with none yet is evaluated over the newest event its provider holds, read once without claiming or delivering it; that event is kept as the link's sample and echoed back as `sampleEvent` (`sampleSource: fetched`). When that read finds nothing or fails (an empty mailbox, an expired credential, a timeout), the answer is over the descriptor's sample (`sampleSource: declared`), never an error.\n\nA webhook trigger has no feed to read and its descriptor's sample comes from another account, so until the named link receives its first event nothing is evaluated: the answer is `{ output: null, issues: [], sampleSource: none }`, with no key and no verdict on the mapping. The same holds when no `subscriptionId` is given. A key or a destination input schema that does not compile is still refused with its 400, since it is wrong on any event.\n\nA blank or `null` `mapping` maps the way the link delivers: on a workflow it is the identity mapping and the sample event comes back unchanged; on an agent the sample event comes back unchanged when it carries a non-empty text `message`, and otherwise as `{ message }` holding the event as text (a string as is, anything else as JSON). `output` is whatever the expression produced, which may be a scalar or an array as easily as an object.\n\nThe key the link's kind reads is evaluated over the same sample event: `correlationKey` on a workflow link (the run a real event would resume), `conversationKey` on an agent link (the conversation it would land in). As on register, a non-blank key of the other kind is refused (`invalid_payload`); a blank one (null, empty or whitespace) is no key.\n\nPresent only on a box with an inbound-trigger intake armed.",
|
|
101642
102076
|
request: { body: jsonBody(named("PreviewTriggerMappingRequest", previewTriggerMappingRequestSchema), "The capability to read the sample event from, the workflow or agent to validate against, and the candidate `mapping` (plus the optional `correlationKey` or `conversationKey`) to try.") },
|
|
101643
102077
|
responses: {
|
|
101644
|
-
200: jsonResponse$
|
|
101645
|
-
400: jsonResponse$
|
|
102078
|
+
200: jsonResponse$2(named("TriggerMappingPreview", triggerMappingPreviewResponseSchema), "The mapped output, plus one issue per field the target schema rejected. An empty `issues` with a 200 IS the passing dry-run, except under `sampleSource: none`: a webhook trigger with no event yet, where nothing was evaluated and `output` is `null`. Otherwise `sampleSource` says which sample event was evaluated over, and `sampleEvent` is present only when it was `fetched`. `correlationKey` and `conversationKey` are each present only when the request carried a non-blank expression for it, and `null` when that expression matched nothing."),
|
|
102079
|
+
400: jsonResponse$2(named("TriggerMappingRefusal", triggerMappingRefusalSchema), "The body failed its schema (`invalid_payload`, including a key the link's kind does not read), the capability does not resolve (`unknown_capability`), or the operator's own expression is at fault: `invalid_mapping` / `invalid_correlation_key` / `invalid_conversation_key` (it does not parse) and `mapping_evaluation_failed` / `correlation_key_evaluation_failed` / `conversation_key_evaluation_failed` (it parsed and threw). A `invalid_target_schema` here is the WORKFLOW's declared input schema failing to compile — reported rather than answered as a passing dry-run with no issues."),
|
|
101646
102080
|
404: errorResponse("This box holds no destination of that kind and name (`workflow_not_found` or `agent_not_found`). Platform-installed agents are unavailable as destinations."),
|
|
101647
102081
|
501: errorResponse("This runtime wired no destination-schema resolver, so there is nothing to validate against (`preview_unavailable`).")
|
|
101648
102082
|
}
|
|
@@ -101672,6 +102106,22 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101672
102106
|
trigger,
|
|
101673
102107
|
subscriptionId
|
|
101674
102108
|
});
|
|
102109
|
+
if (sample.source === "none") {
|
|
102110
|
+
const correlation = compileKey(correlationKey, "correlationKey");
|
|
102111
|
+
if (!correlation.ok) return c.json(correlation.refusal, 400);
|
|
102112
|
+
const conversation = compileKey(conversationKey, "conversationKey");
|
|
102113
|
+
if (!conversation.ok) return c.json(conversation.refusal, 400);
|
|
102114
|
+
const declared = schemaPair.input;
|
|
102115
|
+
if (declared != null && typeof declared === "object") {
|
|
102116
|
+
const schemaRefusal = targetSchemaRefusal(validateAgainstSchema(null, declared));
|
|
102117
|
+
if (schemaRefusal) return c.json(schemaRefusal, 400);
|
|
102118
|
+
}
|
|
102119
|
+
return c.json({
|
|
102120
|
+
output: null,
|
|
102121
|
+
issues: [],
|
|
102122
|
+
sampleSource: sample.source
|
|
102123
|
+
}, 200);
|
|
102124
|
+
}
|
|
101675
102125
|
const sampleData = sample.event;
|
|
101676
102126
|
const source = {
|
|
101677
102127
|
sampleSource: sample.source,
|
|
@@ -101703,11 +102153,8 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101703
102153
|
...source
|
|
101704
102154
|
}, 200);
|
|
101705
102155
|
const validated = validateAgainstSchema(output, inputSchema);
|
|
101706
|
-
|
|
101707
|
-
|
|
101708
|
-
message: validated.error.message,
|
|
101709
|
-
error: validated.error
|
|
101710
|
-
}, 400);
|
|
102156
|
+
const schemaRefusal = targetSchemaRefusal(validated);
|
|
102157
|
+
if (schemaRefusal) return c.json(schemaRefusal, 400);
|
|
101711
102158
|
const issues = !validated.ok && validated.error.kind === "schema_validation_error" ? validated.error.issues : [];
|
|
101712
102159
|
return c.json({
|
|
101713
102160
|
output,
|
|
@@ -101725,7 +102172,7 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101725
102172
|
description: "Runs the reserved `mapping-suggest` workflow inline over the org's model, grounded on the sample event — the first real event of the link `subscriptionId` names when it has one, else the trigger's own — and on the trigger's declared event schema, when it has one, and aimed at the destination input JSON Schema: the workflow's declared schema, or an agent's required `message` string with at least one character. The answer is a pair-list, not a mapping — the editor materialises it, and the operator still has to save.\n\nThe conversation key is offered as a named output too, so a suggestion may target `@correlationKey` instead of a destination input field.\n\nPresent only on a box with an inbound-trigger intake armed.",
|
|
101726
102173
|
request: { body: jsonBody(named("AutoMapTriggerMappingRequest", autoMapTriggerMappingRequestSchema), "The capability to map FROM and the workflow or agent to map TO.") },
|
|
101727
102174
|
responses: {
|
|
101728
|
-
200: jsonResponse$
|
|
102175
|
+
200: jsonResponse$2(named("TriggerAutoMapProposal", triggerAutoMapResponseSchema), "The model's proposed pairs, each a target path and the JSONata that fills it. An empty list is a legitimate answer."),
|
|
101729
102176
|
403: forbiddenResponse("config:write"),
|
|
101730
102177
|
400: errorResponse("The body failed its schema (`invalid_payload`), the capability does not resolve (`unknown_capability`), or the workflow declares no input schema, so there is nothing to map to (`no_target_schema`)."),
|
|
101731
102178
|
404: errorResponse("This box holds no destination of that kind and name (`workflow_not_found` or `agent_not_found`). Platform-installed agents are unavailable as destinations."),
|
|
@@ -101787,7 +102234,7 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101787
102234
|
description: "Runs the options loader of a scope param the capability flags `dynamicOptions: true` (a piece dropdown, such as WhatsApp's business number) through the credential of `principalKey`, and answers the values it may take. Each `value` is the text a link stores under the param and is sent back as is on register: a string the piece answered stays that string, and anything else is its JSON. Writes nothing and arms nothing.\n\nPresent only on a box with an inbound-trigger intake armed.",
|
|
101788
102235
|
request: { body: jsonBody(named("TriggerScopeOptionsRequest", triggerScopeOptionsRequestSchema), "The capability, the param to load, the principal whose credential loads it, and the scope drafted so far.") },
|
|
101789
102236
|
responses: {
|
|
101790
|
-
200: jsonResponse$
|
|
102237
|
+
200: jsonResponse$2(named("TriggerScopeOptions", triggerScopeOptionsResponseSchema), "The options, in the piece's order, with its `placeholder` and `disabled` when it gave them. An empty list is a legitimate answer: the account has none."),
|
|
101791
102238
|
403: forbiddenResponse("config:write"),
|
|
101792
102239
|
400: errorResponse("The body failed its schema (`invalid_payload`), the param is typed rather than filled by the provider (`not_dynamic`), or the options could not be loaded for a reason somebody has to fix first: the broker's own code when the principal's credential could not be issued (`no_token`, `user_authorization_required`, `invalid_grant`, `credential_error`), `scope_options_failed` otherwise, with the piece's message."),
|
|
101793
102240
|
404: errorResponse("No such trigger on that integration (`unknown_capability`), or the trigger declares no scope param by that name (`unknown_scope_param`)."),
|
|
@@ -101839,7 +102286,7 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101839
102286
|
tags: [TRIGGER_SUBSCRIPTIONS_TAG],
|
|
101840
102287
|
summary: "List every trigger → workflow link",
|
|
101841
102288
|
description: "Each link with the rollup of what it has actually done — when it last polled, when it last fired, and how many events it started or turned away. Polling and firing are two separate facts: an inbox with no new mail is a healthy link that has never fired.\n\n`activity.echoLoop` is set when a run the link started wrote back through the link's own connector with an `echoPath`, inside the last hour, and never asked `isOwnEcho` about that connector: the workflow may start itself. One per link, naming the connector, the operation and the run of the newest such write; `null` for a link that is off.\n\nPresent only on a box with an inbound-trigger intake armed.",
|
|
101842
|
-
responses: { 200: jsonResponse$
|
|
102289
|
+
responses: { 200: jsonResponse$2(named("TriggerSubscriptionListResponse", triggerSubscriptionListResponseSchema), "Every link, plus the cadence the scheduler polls them at.") }
|
|
101843
102290
|
}), async (c) => c.json({
|
|
101844
102291
|
subscriptions: await deps.store.list(),
|
|
101845
102292
|
supportsResumeFromNow: true,
|
|
@@ -101853,7 +102300,7 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101853
102300
|
description: "The operator-owned link WITHOUT the activity rollup the list carries — this read is also the intake tick's own, once per link per poll cadence, and joining the delivery journal on every tick would pay for it forever.\n\nAn armed link on a webhook trigger also carries `webhookUrl`, where the provider POSTs its events, for a piece that registers nothing itself and leaves the operator to paste it in.",
|
|
101854
102301
|
request: { params: looseSubscriptionIdPath },
|
|
101855
102302
|
responses: {
|
|
101856
|
-
200: jsonResponse$
|
|
102303
|
+
200: jsonResponse$2(named("TriggerSubscriptionResponse", triggerSubscriptionResponseSchema), "The link."),
|
|
101857
102304
|
404: errorResponse("This box holds no link with that id (`not_found`).")
|
|
101858
102305
|
}
|
|
101859
102306
|
}), async (c) => {
|
|
@@ -101883,7 +102330,7 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
101883
102330
|
body: jsonBody(named("PatchTriggerSubscriptionRequest", patchTriggerSubscriptionRequestSchema), "The knobs a link can change after registration. Every field is optional on its own, but the body may not be empty.")
|
|
101884
102331
|
},
|
|
101885
102332
|
responses: {
|
|
101886
|
-
200: jsonResponse$
|
|
102333
|
+
200: jsonResponse$2(named("TriggerSubscriptionResponse", triggerSubscriptionResponseSchema), "The link as it now stands."),
|
|
101887
102334
|
403: forbiddenResponse("config:write"),
|
|
101888
102335
|
400: errorResponse("The body failed its schema — including the \"provide at least one field\" rule — (`invalid_payload`), it gives the link the key of the other kind (`invalid_payload`), it would leave a `debounceMs` on a workflow link or on an agent link with no `conversationKey` (`invalid_payload`), or the new JSONata does not compile (`invalid_mapping`, `invalid_correlation_key`, `invalid_conversation_key`). A patch that TURNS THE LINK ON is refused on the mapping it would leave behind when that mapping cannot fill the destination, a blank or `null` one judged on the default its kind delivers, as on register (`mapping_evaluation_failed`, `mapping_cannot_produce_input` — `details.mappingIssues` names the offending paths), and when a piece trigger's own `onEnable` fails (`trigger_enable_failed`, with the piece's message, or the broker's own code when the link's credential could not be minted), or when a piece's webhook trigger has no public URL to register (`webhook_url_unavailable`). A piece's webhook link that has received no event yet is not judged on its mapping; from its first event on, every save that leaves it armed is judged on that event. A `scope` edit is refused on the same two checks the register path runs (`scope_required`, `invalid_scope`), and — when the link stays armed — on the connection fence the new scope resolves onto (`operation_not_permitted`) and on a connector no request could reach (`connector_installation_required`). `resumeFrom` on a body that does not turn the link on is `invalid_payload`, `resumeFrom: \"cursor\"` on a link that has never polled is `no_cursor_to_resume_from` (it has no bookmark to resume from), and `resumeFrom: \"now\"` when no cursor can be minted for \"now\" is `cursor_seed_failed` — clearing the stored one instead would replay the provider's whole history. Nothing is written, `enabled` is left untouched, the previous expression stands and the cursor is not moved."),
|
|
101889
102336
|
404: errorResponse("This box holds no link with that id (`not_found`)."),
|
|
@@ -102039,7 +102486,7 @@ var init_subscriptions_router = __esmMin((() => {
|
|
|
102039
102486
|
query: deleteTriggerSubscriptionQuery
|
|
102040
102487
|
},
|
|
102041
102488
|
responses: {
|
|
102042
|
-
200: jsonResponse$
|
|
102489
|
+
200: jsonResponse$2(named("TriggerSubscriptionDeleteResponse", triggerSubscriptionDeleteResponseSchema), "The link is gone and its repeatable is disarmed."),
|
|
102043
102490
|
403: forbiddenResponse("config:write"),
|
|
102044
102491
|
404: errorResponse("This box holds no link with that id (`not_found`)."),
|
|
102045
102492
|
502: errorResponse("The piece trigger's own `onDisable` failed (`trigger_disable_failed`), so whatever it registered at the provider may still be there. Nothing was removed and the link is still armed; retry, or send `?force=true` to delete it anyway.")
|
|
@@ -102189,7 +102636,7 @@ var init_webhook_router = __esmMin((() => {
|
|
|
102189
102636
|
body: describedJsonBody({}, "The provider's event, as it sends it. JSON is parsed, a form is read as its fields, and anything else reaches the trigger as text.")
|
|
102190
102637
|
},
|
|
102191
102638
|
responses: {
|
|
102192
|
-
200: jsonResponse$
|
|
102639
|
+
200: jsonResponse$2(webhookReceiptSchema, "Every event in the delivery reached the journal."),
|
|
102193
102640
|
400: errorResponse("The body is declared JSON and does not parse (`invalid_payload`)."),
|
|
102194
102641
|
404: errorResponse("No link is armed at this URL (`not_found`): the id names no link, the link is off or is not a webhook trigger, or the token is not its own. One answer for all of them."),
|
|
102195
102642
|
413: errorResponse("The body is over the delivery cap (`payload_too_large`)."),
|
|
@@ -102833,7 +103280,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
102833
103280
|
description: "The SDK's call, signed with the box HMAC like `POST /api/connect/execute`. The SDK has already staged the bytes in the box's object store; the body names the key, never the bytes. The box resolves the output's binding, runs the connector's file writer through the action executor — so the connection fence applies to every call the sequence makes — and answers a RECEIPT.\n\nA receipt, not an error, because by the time a run reaches its output the expensive work is done. `filed: true` carries the provider's file id. `filed: false` carries the parked row: `unbound` when nobody has bound this output yet, `write_failed` when the provider refused — the row keeps the staged key so the document is not lost, and the installation screen lists it.\n\nThe one error is a binding this box cannot act on (`destination_misconfigured`): a connector with no file writer, or a destination with no `id`. Retrying cannot fix that, so nothing is parked for it.",
|
|
102834
103281
|
request: { body: jsonBody(named("OutputWriteRequest", outputWriteRequestSchema), "Which output of which workflow, the name and type the document gets, and the staged key.") },
|
|
102835
103282
|
responses: {
|
|
102836
|
-
200: jsonResponse$
|
|
103283
|
+
200: jsonResponse$2(named("OutputWriteReceipt", outputWriteReceiptSchema), "The document is at the provider (`filed`), or kept on this box (`parked`)."),
|
|
102837
103284
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
102838
103285
|
401: AGENT_WIRE_401,
|
|
102839
103286
|
409: errorResponse("The binding cannot be acted on (`destination_misconfigured`): its connector declares no file writer, names one this box has no code for, or stores a destination with no `id`. Fix the binding; nothing was parked.")
|
|
@@ -102861,7 +103308,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
102861
103308
|
description: "The SDK's other call, signed with the box HMAC exactly as `POST /write` is. The box resolves the action's binding, builds the same executor `/api/connect/execute` builds, and runs ONE operation through it.\n\nWhat crosses is smaller than a write's body because the decisions split differently: the WORKFLOW names the operation and composes the arguments, and the operator picked the connection and nothing else. There is no destination, no staged bytes and no file name.\n\nAnswers a RESULT, not an error, for the reason the write answers a receipt: by the time a run reaches its action the expensive work is done. `called: true` carries the operation's own output. `called: false` carries a refusal — `unbound` when nobody has bound this action yet, and otherwise the BROKER's own code and message, forwarded untouched. PLA-1093's connection fence lands in that arm, naming the operation attempted and the ones the connection permits, and it refuses before any credential is minted.",
|
|
102862
103309
|
request: { body: jsonBody(named("ActionCallRequest", actionCallRequestSchema), "Which action of which workflow, the operation to run, and its arguments.") },
|
|
102863
103310
|
responses: {
|
|
102864
|
-
200: jsonResponse$
|
|
103311
|
+
200: jsonResponse$2(named("ActionCallResult", actionCallResultSchema), "The operation ran (`called`), or nothing was called and the refusal says why."),
|
|
102865
103312
|
400: errorResponse("The body failed its schema (`invalid_payload`)."),
|
|
102866
103313
|
401: AGENT_WIRE_401
|
|
102867
103314
|
}
|
|
@@ -102875,7 +103322,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
102875
103322
|
tags: [DESTINATIONS_TAG],
|
|
102876
103323
|
summary: "List every output binding",
|
|
102877
103324
|
description: "Each declared output an operator has bound: which workflow, which output, which connector and grant, and the destination as the operator stored it.\n\nPresent only on a box with an inbound-trigger intake armed.",
|
|
102878
|
-
responses: { 200: jsonResponse$
|
|
103325
|
+
responses: { 200: jsonResponse$2(named("DestinationBindingListResponse", destinationBindingListResponseSchema), "Every binding on this box.") }
|
|
102879
103326
|
}), async (c) => c.json({ bindings: await deps.store.listBindings() }, 200));
|
|
102880
103327
|
app.openapi(createRoute({
|
|
102881
103328
|
method: "post",
|
|
@@ -102885,7 +103332,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
102885
103332
|
description: "Decides how one of a workflow's declarations reaches the outside. WHICH rules apply comes from what the workspace declared the name as, never from the body: a `file` needs a connector that can file and a destination it accepts; an `action` needs a connector this box knows and NO destination, because the operator picks the connection and nothing else. A name nothing declares takes the file rules.\n\nAn UPSERT on the (workflow, output) pair: binding an output that is already bound replaces the binding and keeps its id, because the later decision is the one that holds. `principalKey` defaults to `app`.\n\nThe connector must be one `GET /api/destinations/capabilities` lists — a name this box does not know, or one with no file writer, is refused here rather than days later on a run nobody is watching — and `principalKey` must be a grant the broker can resolve.\n\n`destination` is stored as the operator sent it; the one shape rule is an `id` string, which is what the connector's writer receives. Every other key is display metadata the box never reads.",
|
|
102886
103333
|
request: { body: jsonBody(named("UpsertDestinationBindingRequest", upsertDestinationBindingRequestSchema), "The output to bind, the connector and grant to write through, and where.") },
|
|
102887
103334
|
responses: {
|
|
102888
|
-
201: jsonResponse$
|
|
103335
|
+
201: jsonResponse$2(named("DestinationBindingResponse", destinationBindingResponseSchema), "The binding as it was stored."),
|
|
102889
103336
|
400: errorResponse("The body failed its schema (`invalid_payload`), names a connector this box does not know or that cannot file a document (`unknown_connector`, `connector_cannot_file`), names a grant the broker cannot resolve (`invalid_principal_key`), or sends a destination the declaration does not accept — missing or malformed for a `file` (`destination_required`, `invalid_destination`), present at all for an `action` (`destination_not_accepted`).")
|
|
102890
103337
|
}
|
|
102891
103338
|
}), async (c) => {
|
|
@@ -102909,7 +103356,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
102909
103356
|
summary: "Read one output binding",
|
|
102910
103357
|
request: { params: looseBindingIdPath },
|
|
102911
103358
|
responses: {
|
|
102912
|
-
200: jsonResponse$
|
|
103359
|
+
200: jsonResponse$2(named("DestinationBindingResponse", destinationBindingResponseSchema), "The binding."),
|
|
102913
103360
|
404: errorResponse("This box holds no binding with that id (`not_found`).")
|
|
102914
103361
|
}
|
|
102915
103362
|
}), async (c) => {
|
|
@@ -102927,7 +103374,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
102927
103374
|
body: jsonBody(named("PatchDestinationBindingRequest", patchDestinationBindingRequestSchema), "The fields to change.")
|
|
102928
103375
|
},
|
|
102929
103376
|
responses: {
|
|
102930
|
-
200: jsonResponse$
|
|
103377
|
+
200: jsonResponse$2(named("DestinationBindingResponse", destinationBindingResponseSchema), "The binding after the edit."),
|
|
102931
103378
|
400: errorResponse("The body failed its schema (`invalid_payload`), carried none of the three editable fields, named a connector this box cannot file through (`unknown_connector`, `connector_cannot_file`), named a grant the broker cannot resolve (`invalid_principal_key`), or sent a destination the declaration does not accept (`destination_required`, `invalid_destination`, `destination_not_accepted`)."),
|
|
102932
103379
|
404: errorResponse("This box holds no binding with that id (`not_found`).")
|
|
102933
103380
|
}
|
|
@@ -102961,7 +103408,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
102961
103408
|
description: "Removes the binding. The next write for that output parks as `unbound` instead of failing. A repeat delete answers 404, so a client can tell \"I removed it\" from \"it was already gone\".",
|
|
102962
103409
|
request: { params: looseBindingIdPath },
|
|
102963
103410
|
responses: {
|
|
102964
|
-
200: jsonResponse$
|
|
103411
|
+
200: jsonResponse$2(named("DestinationBindingDeleteResponse", destinationBindingDeleteResponseSchema), "The binding is gone."),
|
|
102965
103412
|
404: errorResponse("This box holds no binding with that id (`not_found`).")
|
|
102966
103413
|
}
|
|
102967
103414
|
}), async (c) => {
|
|
@@ -102983,7 +103430,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
102983
103430
|
tags: [DESTINATIONS_TAG],
|
|
102984
103431
|
summary: "List the documents waiting to be filed",
|
|
102985
103432
|
description: "Every output a run produced that has not reached its destination, newest first: `unbound` rows waiting for a binding, `write_failed` rows carrying what the provider said and how many attempts have been made. A row leaves this list once a later attempt files it — `POST {id}/drain` is that attempt.",
|
|
102986
|
-
responses: { 200: jsonResponse$
|
|
103433
|
+
responses: { 200: jsonResponse$2(named("ParkedOutputListResponse", parkedOutputListResponseSchema), "What is still waiting.") }
|
|
102987
103434
|
}), async (c) => c.json({ parked: (await deps.store.listParked()).map(parkedListRow) }, 200));
|
|
102988
103435
|
app.openapi(createRoute({
|
|
102989
103436
|
method: "get",
|
|
@@ -102993,7 +103440,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
102993
103440
|
description: "The values behind a parked call, one row at a time (PLA-1273).\n\nIts own route because the arguments ARE the write: a workflow composes them freely, `stackbone.secrets` included, so handing them to an operator deciding whether to press Drain is a disclosure and not a lookup. The list route carries the argument NAMES and stops there — the operator sees the shape of the call unasked, and asks for what is inside it.\n\nEvery answered read leaves a `destination.reveal_args` row on the box's activity trail, naming the workflow, the output, the operation and the person. Never a key and never a value: a trail carrying the argument names of a call would put half the disclosure back into the record that exists to account for it.\n\n`{}` is a real answer, not an empty one. A document was never a call, and an action whose run composed nothing is still a row an operator can open — the kind of the row is not a reason to fail a read.",
|
|
102994
103441
|
request: { params: looseBindingIdPath },
|
|
102995
103442
|
responses: {
|
|
102996
|
-
200: jsonResponse$
|
|
103443
|
+
200: jsonResponse$2(named("ParkedOutputArgsResponse", parkedOutputArgsResponseSchema), "The argument object the run composed, whole."),
|
|
102997
103444
|
404: errorResponse("No parked row with that id on this box (`not_found`).")
|
|
102998
103445
|
}
|
|
102999
103446
|
}), async (c) => {
|
|
@@ -103022,7 +103469,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
103022
103469
|
tags: [DESTINATIONS_TAG],
|
|
103023
103470
|
summary: "List what an output can be bound through",
|
|
103024
103471
|
description: "Every connector on this box that can file a document — one declaring a `fileWriter` — and the fields an operator must fill in to say where it lands.\n\nThese are the connector's OWN `destinationParams`, not a trigger's scope params: a binding is not a trigger, and a write-only provider has no trigger to borrow from.\n\n`pattern` is deliberately absent, as it is on the trigger capabilities surface. The regex is an escape boundary the box enforces on the way in; a client holding a copy is a client that can disagree with the box about what is valid.",
|
|
103025
|
-
responses: { 200: jsonResponse$
|
|
103472
|
+
responses: { 200: jsonResponse$2(named("DestinationCapabilityListResponse", destinationCapabilityListResponseSchema), "What this box can file through, and what each needs.") }
|
|
103026
103473
|
}), async (c) => c.json({ capabilities: destinationCapabilities(deps.definitions) }, 200));
|
|
103027
103474
|
app.openapi(createRoute({
|
|
103028
103475
|
method: "post",
|
|
@@ -103032,7 +103479,7 @@ var init_destinations_router = __esmMin((() => {
|
|
|
103032
103479
|
description: "The operator's button, one row at a time. Deliberately NOT a sweep: this happens once per customer, at setup, with a human present — which is exactly when a failure is read and when nothing should be happening behind anyone's back.\n\nWHICH KIND the row is decides what runs: a document goes through the file writer, an action through the action caller (PLA-1253). The operator presses one button either way, because the question they are answering is one question.\n\nThe binding is re-read NOW, not as it was when the row parked, so an `unbound` row goes out as soon as its output has been bound.\n\nAnswers a RECEIPT, and the two shapes differ in one field. A document lands as `filed: true` with the provider's id; an action as `fired: true` with whatever the operation returned, which may be a list, a boolean, or nothing. The failing arm is identical: the PROVIDER'S OWN message and the row's attempt count — the operator pressing the button is the person who can act on \"this connection does not permit createIssue\", so it is not flattened into a status code.",
|
|
103033
103480
|
request: { params: looseBindingIdPath },
|
|
103034
103481
|
responses: {
|
|
103035
|
-
200: jsonResponse$
|
|
103482
|
+
200: jsonResponse$2(named("ParkedDrainReceipt", z.union([outputDrainReceiptSchema, actionDrainReceiptSchema])), "It is at the provider, or it is still here and this is why."),
|
|
103036
103483
|
404: errorResponse("No parked row with that id on this box (`not_found`)."),
|
|
103037
103484
|
409: errorResponse("Nothing to send: the row already went out or another drain has it right now (`already_filed`, `already_fired`), nobody has bound it yet (`unbound`), or its binding cannot be acted on (`destination_misconfigured`). None is fixed by pressing again.")
|
|
103038
103485
|
}
|
|
@@ -103237,7 +103684,7 @@ var init_discovery_handlers = __esmMin((() => {
|
|
|
103237
103684
|
agents: [...manifest.agents, ...deepAgentManifestEntries(ctx.getDeepAgentRegistry?.())]
|
|
103238
103685
|
};
|
|
103239
103686
|
};
|
|
103240
|
-
const discoveryResponses = { 200: jsonResponse$
|
|
103687
|
+
const discoveryResponses = { 200: jsonResponse$2(discoveryResponseSchema, "Everything this workspace serves: its in-process deep agents, and its workflows.") };
|
|
103241
103688
|
app.openapi(createRoute({
|
|
103242
103689
|
method: "get",
|
|
103243
103690
|
path: "/api/discovery",
|
|
@@ -103260,7 +103707,7 @@ var init_discovery_handlers = __esmMin((() => {
|
|
|
103260
103707
|
tags: [WORKFLOWS_TAG],
|
|
103261
103708
|
summary: "List the workflows this box serves",
|
|
103262
103709
|
description: "The catalog rows a creator is offered by default. A workflow whose compile FAILED keeps its row, marked `degraded` with the compiler's own message, rather than vanishing from the list. The platform's own entries (`rag-ingest`, `mapping-suggest`) are hidden here — `GET /api/catalog` returns them flagged.",
|
|
103263
|
-
responses: { 200: jsonResponse$
|
|
103710
|
+
responses: { 200: jsonResponse$2(workflowListResponseSchema, "The workflow rows.") }
|
|
103264
103711
|
}), (c) => c.json({ items: ctx.set.manifest().workflows }, 200));
|
|
103265
103712
|
app.openapi(createRoute({
|
|
103266
103713
|
method: "get",
|
|
@@ -103270,8 +103717,8 @@ var init_discovery_handlers = __esmMin((() => {
|
|
|
103270
103717
|
description: "Resolved against the UNFILTERED catalog rows, so a platform-owned entry the catalog lists can be opened. `steps` is heuristic — the WDK manifest keys steps by source FILE, so a step imported from a shared module is missing.",
|
|
103271
103718
|
request: { params: workflowNamePath },
|
|
103272
103719
|
responses: {
|
|
103273
|
-
200: jsonResponse$
|
|
103274
|
-
404: jsonResponse$
|
|
103720
|
+
200: jsonResponse$2(workflowDetailSchema, "The workflow row."),
|
|
103721
|
+
404: jsonResponse$2(workflowNotFoundBodySchema, "This workspace declares no workflow by that name. The names it does declare are in `details.known`.")
|
|
103275
103722
|
}
|
|
103276
103723
|
}), (c) => {
|
|
103277
103724
|
const { name } = c.req.valid("param");
|
|
@@ -103291,9 +103738,9 @@ var init_discovery_handlers = __esmMin((() => {
|
|
|
103291
103738
|
description: "The input/output JSON Schema the build folded onto this workflow. A known workflow that declared none answers `{ input: null, output: null }` — the Playground falls back to a raw-JSON field.",
|
|
103292
103739
|
request: { params: workflowNamePath },
|
|
103293
103740
|
responses: {
|
|
103294
|
-
200: jsonResponse$
|
|
103295
|
-
404: jsonResponse$
|
|
103296
|
-
503: jsonResponse$
|
|
103741
|
+
200: jsonResponse$2(workflowSchemaPairSchema, "The folded contract, either half nullable."),
|
|
103742
|
+
404: jsonResponse$2(workflowNotFoundBodySchema, "No workflow by that name."),
|
|
103743
|
+
503: jsonResponse$2(workflowNotReadyBodySchema, "The workflow is REGISTERED but cannot answer — its compile failed, or no runtime is armed. Never a 404: the entry exists, so the reason is the compiler's own message rather than \"unknown workflow\".")
|
|
103297
103744
|
}
|
|
103298
103745
|
}), (c) => {
|
|
103299
103746
|
const { name } = c.req.valid("param");
|
|
@@ -103342,12 +103789,12 @@ var init_start_handlers = __esmMin((() => {
|
|
|
103342
103789
|
body: describedJsonBody(workflowInputBodySchema, "The workflow's own input. Validated against the contract the creator declared (`GET /api/workflows/{name}/schema`), not against a box schema. Omit it entirely for a workflow that takes none.")
|
|
103343
103790
|
},
|
|
103344
103791
|
responses: {
|
|
103345
|
-
200: jsonResponse$
|
|
103346
|
-
400: jsonResponse$
|
|
103347
|
-
403: jsonResponse$
|
|
103348
|
-
404: jsonResponse$
|
|
103349
|
-
422: jsonResponse$
|
|
103350
|
-
503: jsonResponse$
|
|
103792
|
+
200: jsonResponse$2(workflowStartReceiptSchema, "The run was enqueued — or, for a `serial` workflow whose lock is held, the trigger was queued behind the active run."),
|
|
103793
|
+
400: jsonResponse$2(workflowInputInvalidBodySchema, "The workflow's own declared validator rejected this input. NO run is started."),
|
|
103794
|
+
403: jsonResponse$2(z.union([invocationDeniedBodySchema, errorEnvelopeSchema]), "Refused before the handler ran, for one of two reasons. `invocation_not_permitted`: the caller is a workspace API key whose grants do not name this workflow under `workflow:start` (`http/invocation-guard.ts`). `forbidden`: the caller's verified identity does not hold `runs:invoke` — only reachable on the browser-direct datapath, since a proxy-signed call was authorized before it was signed. No run is started either way."),
|
|
103795
|
+
404: jsonResponse$2(workflowNotFoundBodySchema, "This workspace declares no workflow by that name. The names it does declare are in `details.known`."),
|
|
103796
|
+
422: jsonResponse$2(workflowGuardrailBlockedBodySchema, "The operator's own guardrail policy declined this payload. A clean 4xx, never a 500 — the request was understood, and the body names WHICH rule refused it."),
|
|
103797
|
+
503: jsonResponse$2(workflowNotReadyBodySchema, "The workflow is REGISTERED but cannot answer — its compile failed, or no runtime is armed. Never the 404 a typo gets: the entry exists, so the body carries the compiler's own message.")
|
|
103351
103798
|
}
|
|
103352
103799
|
}), async (c) => {
|
|
103353
103800
|
const { name } = c.req.valid("param");
|
|
@@ -103440,12 +103887,12 @@ var init_chat_handlers = __esmMin((() => {
|
|
|
103440
103887
|
},
|
|
103441
103888
|
responses: {
|
|
103442
103889
|
200: sseResponse("The turn, frame by frame.", CHAT_FRAMES),
|
|
103443
|
-
400: jsonResponse$
|
|
103444
|
-
403: jsonResponse$
|
|
103445
|
-
404: jsonResponse$
|
|
103446
|
-
409: jsonResponse$
|
|
103447
|
-
422: jsonResponse$
|
|
103448
|
-
503: jsonResponse$
|
|
103890
|
+
400: jsonResponse$2(workflowInputInvalidBodySchema, "The workflow's own declared validator rejected this input. NO run is started, and no stream opens."),
|
|
103891
|
+
403: jsonResponse$2(invocationDeniedBodySchema, "The caller is a workspace API key whose grants do not name this workflow under `workflow:chat` — its own action, so a key that may `/start` this workflow is still refused here. Answered by `http/invocation-guard.ts` BEFORE this handler, and identical for a name that does not exist."),
|
|
103892
|
+
404: jsonResponse$2(workflowNotFoundBodySchema, "No workflow by that name."),
|
|
103893
|
+
409: jsonResponse$2(workflowSerialEntryBodySchema, "The workflow is `serial`. Fire it through `POST /api/workflows/{name}/start` instead: a serial run is queued behind the active one, which a held-open stream cannot wait for."),
|
|
103894
|
+
422: jsonResponse$2(workflowGuardrailBlockedBodySchema, "The operator's own guardrail policy declined this payload — the same border `/start` crosses, re-run here so the streaming door refuses what the durable one refuses."),
|
|
103895
|
+
503: jsonResponse$2(workflowNotReadyBodySchema, "The workflow is REGISTERED but cannot answer — its compile failed, or no runtime is armed.")
|
|
103449
103896
|
}
|
|
103450
103897
|
}), async (c) => {
|
|
103451
103898
|
const { name } = c.req.valid("param");
|
|
@@ -103570,7 +104017,7 @@ var init_hook_handlers = __esmMin((() => {
|
|
|
103570
104017
|
body: describedJsonBody({ description: "Whatever the parked hook was written to receive." }, "What the parked hook receives. Omit it for a hook whose answer is simply \"continue\".")
|
|
103571
104018
|
},
|
|
103572
104019
|
responses: {
|
|
103573
|
-
200: jsonResponse$
|
|
104020
|
+
200: jsonResponse$2(hookResumedResponseSchema, "The run was woken. The F5 sweep is nudged, so the projected run reopens `interrupted → running` without waiting for the next interval."),
|
|
103574
104021
|
400: errorResponse("`invalid_token` — the token segment was empty or whitespace."),
|
|
103575
104022
|
404: errorResponse("`hook_not_found` — no PARKED hook matches that token."),
|
|
103576
104023
|
500: errorResponse("`resume_failed` — the World rejected the resume. `details.cause` carries its message.")
|
|
@@ -103601,7 +104048,7 @@ var init_hook_handlers = __esmMin((() => {
|
|
|
103601
104048
|
description: "The ONLY World-reading route on the box, joined by `runs.trace_id` (= the World run id). Raw `defineHook` hooks are listed too, not just helper-created ones.\n\nRun STATE is deliberately not here — it comes from the projection at `GET /api/runs/{id}`. `resumeKind` is `null` for a hook parked on an EXTERNAL signal, and a run detail must not offer a manual approve for one; a resume-kind lookup that fails degrades every hook to `null` rather than guessing.",
|
|
103602
104049
|
request: { params: z.object({ traceId: z.string().openapi({ description: "The run's World run id — `runs.trace_id`, not `runs.id`. Blank is refused with `invalid_trace_id`." }) }) },
|
|
103603
104050
|
responses: {
|
|
103604
|
-
200: jsonResponse$
|
|
104051
|
+
200: jsonResponse$2(pendingHooksResponseSchema, "The run's pending hooks. An empty list means nothing is parked — never that the run is unknown."),
|
|
103605
104052
|
400: errorResponse("`invalid_trace_id` — the segment was empty or whitespace."),
|
|
103606
104053
|
500: errorResponse("`hooks_read_failed` — the World could not be read. `details.cause` carries its message.")
|
|
103607
104054
|
}
|
|
@@ -104321,7 +104768,7 @@ var buildEmulatorApp = (opts, hooks = {}) => {
|
|
|
104321
104768
|
security: PUBLIC_ROUTE_SECURITY,
|
|
104322
104769
|
summary: "Stackbone Agent Protocol handshake",
|
|
104323
104770
|
description: "The protocol version this box speaks, the oldest it still serves, the capabilities it advertises and the build that answers. The SDK reads it before calling any gated surface.",
|
|
104324
|
-
responses: { 200: jsonResponse$
|
|
104771
|
+
responses: { 200: jsonResponse$2(named("ContractResponse", contractResponseSchema), "The handshake this box answers with.") }
|
|
104325
104772
|
}), (c) => c.json(buildContractResponse({ sdkVersion: opts.sdkVersion }), 200));
|
|
104326
104773
|
app.get("/", (c) => c.html(renderStudioStub({
|
|
104327
104774
|
agentSlug: opts.agent.slug,
|
|
@@ -104336,7 +104783,7 @@ var buildEmulatorApp = (opts, hooks = {}) => {
|
|
|
104336
104783
|
security: PUBLIC_ROUTE_SECURITY,
|
|
104337
104784
|
summary: "Which host this box is",
|
|
104338
104785
|
description: "The cheap dev-facing health read. `host` is the tag the box was booted with; `emulator` is the legacy dev marker and is present on the dev host only.",
|
|
104339
|
-
responses: { 200: jsonResponse$
|
|
104786
|
+
responses: { 200: jsonResponse$2(boxHealthSchema, "The host this box answers as.") }
|
|
104340
104787
|
}), (c) => {
|
|
104341
104788
|
const body = {
|
|
104342
104789
|
status: "ok",
|
|
@@ -104354,7 +104801,7 @@ var buildEmulatorApp = (opts, hooks = {}) => {
|
|
|
104354
104801
|
security: PUBLIC_ROUTE_SECURITY,
|
|
104355
104802
|
summary: "Liveness",
|
|
104356
104803
|
description: "Answers the moment the server binds — no probe, no await. Fly's machine liveness check reads it.",
|
|
104357
|
-
responses: { 200: jsonResponse$
|
|
104804
|
+
responses: { 200: jsonResponse$2(z.object({ status: z.literal("ok") }), "The server is listening.") }
|
|
104358
104805
|
}), (c) => c.json({ status: "ok" }, 200));
|
|
104359
104806
|
app.openapi(createRoute({
|
|
104360
104807
|
method: "get",
|
|
@@ -104364,8 +104811,8 @@ var buildEmulatorApp = (opts, hooks = {}) => {
|
|
|
104364
104811
|
summary: "Subsystem health",
|
|
104365
104812
|
description: "Runs every probe this host declared, in parallel, and reports each one. A host that declares no subsystems answers `checks:{}` — read that as \"nothing to report\", not as proof of health.",
|
|
104366
104813
|
responses: {
|
|
104367
|
-
200: jsonResponse$
|
|
104368
|
-
503: jsonResponse$
|
|
104814
|
+
200: jsonResponse$2(SUBSYSTEM_HEALTH, "Every declared subsystem passed."),
|
|
104815
|
+
503: jsonResponse$2(SUBSYSTEM_HEALTH, "At least one subsystem is failing.")
|
|
104369
104816
|
}
|
|
104370
104817
|
}), async (c) => {
|
|
104371
104818
|
const results = await Promise.all(probes.map(async ([name, probe]) => {
|
|
@@ -104400,7 +104847,7 @@ var buildEmulatorApp = (opts, hooks = {}) => {
|
|
|
104400
104847
|
summary: "Injected service endpoints",
|
|
104401
104848
|
description: "The Postgres, object-storage and agent-process endpoints this box injected into the agent, read back. A creator uses it to point `psql` or an S3 client at the same stack their agent is running against. No secret is in the answer: the Postgres password is removed from the DSN, and the storage secret key is left out (the access key is in).",
|
|
104402
104849
|
responses: {
|
|
104403
|
-
200: jsonResponse$
|
|
104850
|
+
200: jsonResponse$2(boxServicesSchema, "The endpoints this box handed the agent."),
|
|
104404
104851
|
403: errorResponse("The caller's role does not grant `db:query`. Only a browser-direct identity token carries a role; a signed call and the self-host operator token are not checked against one.")
|
|
104405
104852
|
}
|
|
104406
104853
|
}), (c) => c.json({
|
|
@@ -105194,7 +105641,7 @@ var calls_workflow_ts_default = "import { z } from \"zod\";\nimport { streamDeep
|
|
|
105194
105641
|
var plain_workflow_ts_default = "import { z } from \"zod\";\n\n/**\n * Workflow \"{{name}}\" — a durable pipeline.\n *\n * Each `\"use step\"` is a durable checkpoint: kill the runtime mid-run and it\n * resumes from the last completed step instead of replaying everything.\n */\n\n// THE contract — sibling exports next to the bare workflow function. The build\n// harvests these (by convention) into the manifest + a live validator. The\n// parameter type is DERIVED from `inputSchema` and the RETURN type from\n// `outputSchema` (both `z.infer`), so neither side can drift from the contract\n// the build harvests. Without the return annotation the schema is published to\n// Studio and the generated types and never compared to what the body returns\n// (PLA-1272).\nexport const inputSchema = z.object({\n message: z.string().describe(\"The input to process\"),\n});\n\nexport const outputSchema = z.object({\n message: z.string(),\n processed: z.boolean(),\n});\n\ntype {{Name}}Input = z.infer<typeof inputSchema>;\n\nexport async function {{export}}(input: {{Name}}Input): Promise<z.infer<typeof outputSchema>> {\n \"use workflow\";\n\n const result = await processMessage(input); // step 1 — durable checkpoint\n return result;\n}\n\nasync function processMessage(input: {{Name}}Input) {\n \"use step\";\n // Replace this with the deterministic work of your step (DB write, HTTP call,\n // an LLM call, …). Returning a value records it as the step's checkpoint.\n return { message: input.message, processed: true };\n}\n";
|
|
105195
105642
|
//#endregion
|
|
105196
105643
|
//#region src/services/scaffold/template-loader.ts
|
|
105197
|
-
var SDK_TEMPLATE_VERSION = "0.10.
|
|
105644
|
+
var SDK_TEMPLATE_VERSION = "0.10.4";
|
|
105198
105645
|
var PREFIX = "./templates/";
|
|
105199
105646
|
var templates = new Map(Object.entries(/* @__PURE__ */ Object.assign({
|
|
105200
105647
|
"./templates/deep-agents/index.ts.tmpl": index_ts_default,
|
|
@@ -114417,7 +114864,7 @@ var readLocalManifest = async (ctx) => {
|
|
|
114417
114864
|
capabilities: [],
|
|
114418
114865
|
deviations: ["agent.yaml `capabilities` is not a list — skipped capability validation."]
|
|
114419
114866
|
};
|
|
114420
|
-
const known = new Set(CAPABILITIES$
|
|
114867
|
+
const known = new Set(CAPABILITIES$2);
|
|
114421
114868
|
const capabilities = [];
|
|
114422
114869
|
const deviations = [];
|
|
114423
114870
|
for (const entry of declared) if (typeof entry === "string" && known.has(entry)) capabilities.push(entry);
|