@aexhq/sdk 0.38.1 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -17
- package/dist/_contracts/api-key.d.ts +49 -0
- package/dist/_contracts/api-key.js +87 -0
- package/dist/_contracts/bundle-manifest.d.ts +86 -0
- package/dist/_contracts/bundle-manifest.js +157 -0
- package/dist/_contracts/error-codes.d.ts +26 -0
- package/dist/_contracts/error-codes.js +79 -0
- package/dist/_contracts/error-factory.d.ts +32 -0
- package/dist/_contracts/error-factory.js +142 -0
- package/dist/_contracts/event-envelope.d.ts +33 -10
- package/dist/_contracts/event-envelope.js +46 -10
- package/dist/_contracts/event-view.d.ts +123 -0
- package/dist/_contracts/event-view.js +120 -0
- package/dist/_contracts/http.js +12 -3
- package/dist/_contracts/index.d.ts +8 -4
- package/dist/_contracts/index.js +11 -7
- package/dist/_contracts/models.d.ts +15 -0
- package/dist/_contracts/models.js +33 -0
- package/dist/_contracts/operations.d.ts +70 -2
- package/dist/_contracts/operations.js +143 -7
- package/dist/_contracts/run-config.d.ts +35 -10
- package/dist/_contracts/run-config.js +17 -5
- package/dist/_contracts/run-record.d.ts +3 -2
- package/dist/_contracts/runtime-types.d.ts +148 -36
- package/dist/_contracts/runtime-types.js +33 -1
- package/dist/_contracts/sdk-errors.d.ts +61 -2
- package/dist/_contracts/sdk-errors.js +83 -3
- package/dist/_contracts/sdk-secrets.js +31 -6
- package/dist/_contracts/stable.d.ts +14 -0
- package/dist/_contracts/stable.js +14 -0
- package/dist/_contracts/status.d.ts +28 -1
- package/dist/_contracts/status.js +48 -3
- package/dist/_contracts/submission.d.ts +157 -14
- package/dist/_contracts/submission.js +319 -55
- package/dist/_contracts/suggest.d.ts +15 -0
- package/dist/_contracts/suggest.js +54 -0
- package/dist/asset-upload.d.ts +27 -1
- package/dist/asset-upload.js +219 -4
- package/dist/asset-upload.js.map +1 -1
- package/dist/bundle.d.ts +23 -14
- package/dist/bundle.js +39 -20
- package/dist/bundle.js.map +1 -1
- package/dist/canonical-zip.d.ts +68 -0
- package/dist/canonical-zip.js +307 -0
- package/dist/canonical-zip.js.map +1 -0
- package/dist/cli.mjs +1927 -326
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +273 -70
- package/dist/client.js +885 -335
- package/dist/client.js.map +1 -1
- package/dist/fetch-archive.js +14 -14
- package/dist/fetch-archive.js.map +1 -1
- package/dist/file.d.ts +33 -6
- package/dist/file.js +120 -54
- package/dist/file.js.map +1 -1
- package/dist/index.d.ts +23 -14
- package/dist/index.js +31 -14
- package/dist/index.js.map +1 -1
- package/dist/node-fs.d.ts +26 -9
- package/dist/node-fs.js +13 -38
- package/dist/node-fs.js.map +1 -1
- package/dist/node-walk.d.ts +69 -0
- package/dist/node-walk.js +146 -0
- package/dist/node-walk.js.map +1 -0
- package/dist/retry.d.ts +9 -13
- package/dist/retry.js +18 -17
- package/dist/retry.js.map +1 -1
- package/dist/skill.d.ts +151 -0
- package/dist/skill.js +298 -0
- package/dist/skill.js.map +1 -0
- package/dist/tool.d.ts +14 -2
- package/dist/tool.js +33 -7
- package/dist/tool.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/authentication.md +29 -5
- package/docs/billing.md +6 -0
- package/docs/concepts/agent-tools.md +11 -0
- package/docs/concepts/composition.md +3 -3
- package/docs/defaults.md +1 -0
- package/docs/errors.md +64 -4
- package/docs/events.md +84 -49
- package/docs/limits-and-quotas.md +24 -0
- package/docs/mcp.md +3 -2
- package/docs/networking.md +7 -1
- package/docs/outputs.md +36 -7
- package/docs/provider-runtime-capabilities.md +1 -1
- package/docs/quickstart.md +17 -7
- package/docs/run-config.md +3 -3
- package/docs/secrets.md +14 -0
- package/docs/skills.md +74 -44
- package/docs/vision-skills.md +3 -3
- package/examples/feature-tour.ts +4 -6
- package/examples/spike-settle-latency.ts +125 -0
- package/package.json +4 -3
- package/dist/_contracts/event-guards.d.ts +0 -67
- package/dist/_contracts/event-guards.js +0 -36
- package/dist/skill-tool.d.ts +0 -102
- package/dist/skill-tool.js +0 -190
- package/dist/skill-tool.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TOOL_NAME_PATTERN, normaliseSkillBundlePath, parseAssetRefFields, parseMcpServerRef } from "./run-config.js";
|
|
1
|
+
import { SKILL_NAME_PATTERN, SKILL_RESERVED_NAMES, TOOL_NAME_PATTERN, normaliseSkillBundlePath, parseAssetRefFields, parseMcpServerRef } from "./run-config.js";
|
|
2
2
|
import { parseRunTimeout, parseRuntimeSize } from "./runtime-sizes.js";
|
|
3
3
|
import { assertRunModelMatchesProvider, parseRunModel } from "./models.js";
|
|
4
4
|
import { parseRuntimeSecurityProfile } from "./runtime-security-profile.js";
|
|
@@ -554,7 +554,6 @@ export function parseRunSubmissionRequest(input, options = {}) {
|
|
|
554
554
|
assertNoSecretBearingFields(fieldValue, [key]);
|
|
555
555
|
}
|
|
556
556
|
const provider = parseRunProvider(value.provider);
|
|
557
|
-
void options;
|
|
558
557
|
const runtimeSize = parseRuntimeSize(value.runtimeSize);
|
|
559
558
|
const timeoutMs = parseRunTimeout(value.timeout);
|
|
560
559
|
const webhook = parseRunWebhook(value.webhook);
|
|
@@ -562,8 +561,13 @@ export function parseRunSubmissionRequest(input, options = {}) {
|
|
|
562
561
|
const machine = parseRunMachine(value.machine);
|
|
563
562
|
const secrets = parseInlineSecrets(value.secrets);
|
|
564
563
|
enforceCredentialSecretPolicy(secrets, provider);
|
|
565
|
-
const submission = parseSubmission(value.submission
|
|
564
|
+
const submission = parseSubmission(value.submission, {
|
|
565
|
+
trustedReparse: options.trustedReparse === true
|
|
566
|
+
});
|
|
566
567
|
assertRunModelMatchesProvider(provider, submission.model);
|
|
568
|
+
// Fail-closed streaming: `outputMode:'stream'` on a non-streamable provider is
|
|
569
|
+
// a hard reject at parse time (no silent downgrade).
|
|
570
|
+
assertStreamableOutputMode(submission.outputMode, provider);
|
|
567
571
|
crossValidateSecretEnvAndValues(submission.secretEnv, secrets.envSecrets);
|
|
568
572
|
// mcpServers names must agree across the submission half and the
|
|
569
573
|
// secrets half — every secrets.mcpServers[i].name MUST resolve to a
|
|
@@ -647,7 +651,7 @@ export function parseRunLimits(input) {
|
|
|
647
651
|
return undefined;
|
|
648
652
|
}
|
|
649
653
|
const value = requireRecord(input, "limits");
|
|
650
|
-
const allowed = new Set(["maxConcurrentChildRuns", "maxSubagentDepth", "maxSpendUsd"]);
|
|
654
|
+
const allowed = new Set(["maxConcurrentChildRuns", "maxSubagentDepth", "maxSpendUsd", "maxTurns"]);
|
|
651
655
|
for (const key of Object.keys(value)) {
|
|
652
656
|
if (!allowed.has(key)) {
|
|
653
657
|
throw new Error(`limits.${key} is not an allowed field; permitted: ${[...allowed].join(", ")}`);
|
|
@@ -659,16 +663,23 @@ export function parseRunLimits(input) {
|
|
|
659
663
|
// NUMBER, not a positive int. Clamping to the workspace/platform ceiling is the
|
|
660
664
|
// resolver's job; here we only enforce shape + positivity.
|
|
661
665
|
const maxSpendUsd = optionalPositiveNumber(value.maxSpendUsd, "limits.maxSpendUsd");
|
|
666
|
+
// maxTurns is an ITERATION count — a positive safe integer. Clamp to the ceiling
|
|
667
|
+
// is the resolver's job; here we enforce shape + positivity only.
|
|
668
|
+
const maxTurns = optionalPositiveInt(value.maxTurns, "limits.maxTurns");
|
|
662
669
|
// Collapse an all-absent override (e.g. `limits: {}`) to `undefined` so it never
|
|
663
670
|
// lands an empty object on the request — matches sibling parsers (parseRunWebhook,
|
|
664
671
|
// parseEnvironment). The resolver supplies platform defaults for absent fields.
|
|
665
|
-
if (maxConcurrentChildRuns === undefined &&
|
|
672
|
+
if (maxConcurrentChildRuns === undefined &&
|
|
673
|
+
maxSubagentDepth === undefined &&
|
|
674
|
+
maxSpendUsd === undefined &&
|
|
675
|
+
maxTurns === undefined) {
|
|
666
676
|
return undefined;
|
|
667
677
|
}
|
|
668
678
|
return {
|
|
669
679
|
...(maxConcurrentChildRuns !== undefined ? { maxConcurrentChildRuns } : {}),
|
|
670
680
|
...(maxSubagentDepth !== undefined ? { maxSubagentDepth } : {}),
|
|
671
|
-
...(maxSpendUsd !== undefined ? { maxSpendUsd } : {})
|
|
681
|
+
...(maxSpendUsd !== undefined ? { maxSpendUsd } : {}),
|
|
682
|
+
...(maxTurns !== undefined ? { maxTurns } : {})
|
|
672
683
|
};
|
|
673
684
|
}
|
|
674
685
|
/**
|
|
@@ -746,13 +757,15 @@ export function enforceCredentialSecretPolicy(secrets, provider, opts) {
|
|
|
746
757
|
throw new Error(`secrets.apiKeys["${provider}"] is required`);
|
|
747
758
|
}
|
|
748
759
|
}
|
|
749
|
-
export function parseSubmission(input) {
|
|
760
|
+
export function parseSubmission(input, options = {}) {
|
|
750
761
|
const value = requireRecord(input, "submission.submission");
|
|
751
762
|
const allowed = new Set([
|
|
752
763
|
"model",
|
|
753
764
|
"system",
|
|
754
765
|
"prompt",
|
|
755
766
|
"tools",
|
|
767
|
+
"skills",
|
|
768
|
+
"resolvedSkills",
|
|
756
769
|
"agentsMd",
|
|
757
770
|
"files",
|
|
758
771
|
"mcpServers",
|
|
@@ -763,6 +776,8 @@ export function parseSubmission(input) {
|
|
|
763
776
|
"outputs",
|
|
764
777
|
"includeBuiltinTools",
|
|
765
778
|
"outputMode",
|
|
779
|
+
"responseFormat",
|
|
780
|
+
"approvalGate",
|
|
766
781
|
"platform"
|
|
767
782
|
]);
|
|
768
783
|
for (const key of Object.keys(value)) {
|
|
@@ -773,7 +788,9 @@ export function parseSubmission(input) {
|
|
|
773
788
|
const model = parseRunModel(value.model, "submission.model");
|
|
774
789
|
const system = optionalString(value.system, "submission.system");
|
|
775
790
|
const prompt = parsePrompt(value.prompt);
|
|
776
|
-
const { tools,
|
|
791
|
+
const { tools, builtinTools } = parseTools(value.tools);
|
|
792
|
+
const skills = parseSkills(value.skills);
|
|
793
|
+
const resolvedSkills = parseResolvedSkills(value.resolvedSkills, options.trustedReparse === true);
|
|
777
794
|
const agentsMd = parseAgentsMd(value.agentsMd);
|
|
778
795
|
const files = parseFiles(value.files);
|
|
779
796
|
const mcpServers = parseMcpServers(value.mcpServers);
|
|
@@ -784,13 +801,16 @@ export function parseSubmission(input) {
|
|
|
784
801
|
const outputs = parseOutputs(value.outputs);
|
|
785
802
|
const includeBuiltinTools = parseIncludeBuiltinTools(value.includeBuiltinTools);
|
|
786
803
|
const outputMode = parseOutputMode(value.outputMode);
|
|
804
|
+
const responseFormat = parseResponseFormat(value.responseFormat);
|
|
805
|
+
const approvalGate = parseApprovalGate(value.approvalGate);
|
|
787
806
|
const platform = parsePlatformConfig(value.platform);
|
|
788
807
|
return {
|
|
789
808
|
model,
|
|
790
809
|
...(system ? { system } : {}),
|
|
791
810
|
prompt,
|
|
792
811
|
tools,
|
|
793
|
-
...(
|
|
812
|
+
...(skills.length > 0 ? { skills } : {}),
|
|
813
|
+
...(resolvedSkills.length > 0 ? { resolvedSkills } : {}),
|
|
794
814
|
agentsMd,
|
|
795
815
|
files,
|
|
796
816
|
mcpServers,
|
|
@@ -802,6 +822,8 @@ export function parseSubmission(input) {
|
|
|
802
822
|
...(includeBuiltinTools !== undefined ? { includeBuiltinTools } : {}),
|
|
803
823
|
...(builtinTools.length > 0 ? { builtinTools } : {}),
|
|
804
824
|
...(outputMode !== undefined ? { outputMode } : {}),
|
|
825
|
+
...(responseFormat !== undefined ? { responseFormat } : {}),
|
|
826
|
+
...(approvalGate !== undefined ? { approvalGate } : {}),
|
|
805
827
|
...(platform ? { platform } : {})
|
|
806
828
|
};
|
|
807
829
|
}
|
|
@@ -863,6 +885,127 @@ function parseOutputMode(input) {
|
|
|
863
885
|
}
|
|
864
886
|
return input;
|
|
865
887
|
}
|
|
888
|
+
// ---------------------------------------------------------------------------
|
|
889
|
+
// Streaming capability model — WS9. `outputMode:'stream'` is capability-gated.
|
|
890
|
+
// ---------------------------------------------------------------------------
|
|
891
|
+
/**
|
|
892
|
+
* The provider wire-SHAPES that have a real per-token streaming producer. This
|
|
893
|
+
* const is the contracts-side SSoT, pinned EQUAL to the platform's shape SSoT by
|
|
894
|
+
* a cross-repo parity test so streaming can never be promised for a shape
|
|
895
|
+
* nothing feeds.
|
|
896
|
+
*/
|
|
897
|
+
export const STREAMABLE_SHAPES = ["anthropic", "openai_chat"];
|
|
898
|
+
/**
|
|
899
|
+
* Each provider's wire shape, or `null` when it has no streaming producer wired
|
|
900
|
+
* yet. `stream` output is only honored for a provider whose shape is streamable.
|
|
901
|
+
*/
|
|
902
|
+
const PROVIDER_STREAM_SHAPE = {
|
|
903
|
+
anthropic: "anthropic",
|
|
904
|
+
deepseek: "openai_chat",
|
|
905
|
+
openai: "openai_chat",
|
|
906
|
+
gemini: null,
|
|
907
|
+
mistral: "openai_chat",
|
|
908
|
+
openrouter: "openai_chat",
|
|
909
|
+
doubao: "openai_chat",
|
|
910
|
+
"doubao-cn": "openai_chat"
|
|
911
|
+
};
|
|
912
|
+
/** True when a provider has a streaming producer wired (a {@link STREAMABLE_SHAPES} shape). */
|
|
913
|
+
export function isStreamableProvider(provider) {
|
|
914
|
+
return PROVIDER_STREAM_SHAPE[provider] !== null;
|
|
915
|
+
}
|
|
916
|
+
function streamableProviders() {
|
|
917
|
+
return Object.keys(PROVIDER_STREAM_SHAPE).filter(isStreamableProvider);
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Fail-closed streaming gate: `outputMode:'stream'` on a NON-streamable provider
|
|
921
|
+
* throws (a HARD reject — no silent downgrade to buffered). Called by
|
|
922
|
+
* {@link parseRunSubmissionRequest} once mode + provider are both known.
|
|
923
|
+
*/
|
|
924
|
+
export function assertStreamableOutputMode(outputMode, provider) {
|
|
925
|
+
if (outputMode === "stream" && !isStreamableProvider(provider)) {
|
|
926
|
+
throw new Error(`submission.outputMode 'stream' is not supported for provider ${provider}; ` +
|
|
927
|
+
`streaming is available for: ${streamableProviders().join(", ")}`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
// ---------------------------------------------------------------------------
|
|
931
|
+
// Structured-output (schema-decode) policy — WS10.
|
|
932
|
+
// ---------------------------------------------------------------------------
|
|
933
|
+
/** Response-format kinds: free-form `text` (default) or provider-native `json_schema`. */
|
|
934
|
+
export const RESPONSE_FORMAT_KINDS = ["text", "json_schema"];
|
|
935
|
+
/**
|
|
936
|
+
* Parse the optional `submission.responseFormat`. Mirrors {@link parseOutputMode}
|
|
937
|
+
* / {@link OUTPUT_MODES}: absent ⇒ undefined; a bad `kind` or unknown subfield is
|
|
938
|
+
* rejected (fail-fast). `json_schema` requires a JSON-object `schema`.
|
|
939
|
+
*/
|
|
940
|
+
export function parseResponseFormat(input) {
|
|
941
|
+
if (input === undefined || input === null)
|
|
942
|
+
return undefined;
|
|
943
|
+
const value = requireRecord(input, "submission.responseFormat");
|
|
944
|
+
const kind = value.kind;
|
|
945
|
+
if (typeof kind !== "string" || !RESPONSE_FORMAT_KINDS.includes(kind)) {
|
|
946
|
+
throw new Error(`submission.responseFormat.kind must be one of ${RESPONSE_FORMAT_KINDS.join(", ")}`);
|
|
947
|
+
}
|
|
948
|
+
if (kind === "text") {
|
|
949
|
+
for (const key of Object.keys(value)) {
|
|
950
|
+
if (key !== "kind") {
|
|
951
|
+
throw new Error(`submission.responseFormat.${key} is not allowed when kind is 'text'`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return { kind: "text" };
|
|
955
|
+
}
|
|
956
|
+
const allowed = new Set(["kind", "schema", "strict", "name"]);
|
|
957
|
+
for (const key of Object.keys(value)) {
|
|
958
|
+
if (!allowed.has(key)) {
|
|
959
|
+
throw new Error(`submission.responseFormat.${key} is not an allowed field; permitted: ${[...allowed].join(", ")}`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (!isRecord(value.schema) || !isJsonValue(value.schema)) {
|
|
963
|
+
throw new Error("submission.responseFormat.schema must be a JSON-serializable object");
|
|
964
|
+
}
|
|
965
|
+
const schema = value.schema;
|
|
966
|
+
if (value.strict !== undefined && typeof value.strict !== "boolean") {
|
|
967
|
+
throw new Error("submission.responseFormat.strict must be a boolean");
|
|
968
|
+
}
|
|
969
|
+
const name = optionalString(value.name, "submission.responseFormat.name");
|
|
970
|
+
return {
|
|
971
|
+
kind: "json_schema",
|
|
972
|
+
schema,
|
|
973
|
+
...(value.strict !== undefined ? { strict: value.strict } : {}),
|
|
974
|
+
...(name !== undefined ? { name } : {})
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Parse the optional `submission.approvalGate`. Absent / empty tool list ⇒
|
|
979
|
+
* undefined (no gate). Tool names are deduped; the strict allow-list mirrors the
|
|
980
|
+
* sibling parsers.
|
|
981
|
+
*/
|
|
982
|
+
export function parseApprovalGate(input) {
|
|
983
|
+
if (input === undefined || input === null)
|
|
984
|
+
return undefined;
|
|
985
|
+
const value = requireRecord(input, "submission.approvalGate");
|
|
986
|
+
for (const key of Object.keys(value)) {
|
|
987
|
+
if (key !== "tools") {
|
|
988
|
+
throw new Error(`submission.approvalGate.${key} is not an allowed field; permitted: tools`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (!Array.isArray(value.tools)) {
|
|
992
|
+
throw new Error("submission.approvalGate.tools must be an array of tool names");
|
|
993
|
+
}
|
|
994
|
+
const seen = new Set();
|
|
995
|
+
const tools = [];
|
|
996
|
+
value.tools.forEach((entry, index) => {
|
|
997
|
+
if (typeof entry !== "string" || entry.length === 0) {
|
|
998
|
+
throw new Error(`submission.approvalGate.tools[${index}] must be a non-empty string`);
|
|
999
|
+
}
|
|
1000
|
+
if (!seen.has(entry)) {
|
|
1001
|
+
seen.add(entry);
|
|
1002
|
+
tools.push(entry);
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
if (tools.length === 0)
|
|
1006
|
+
return undefined;
|
|
1007
|
+
return { tools };
|
|
1008
|
+
}
|
|
866
1009
|
/**
|
|
867
1010
|
* The CLOSED set of builtin tool NAMES the managed runtime can inject — one per
|
|
868
1011
|
* machine tool the hands implement. This list is the single source of truth for
|
|
@@ -892,7 +1035,10 @@ export const BUILTIN_TOOL_NAMES = [
|
|
|
892
1035
|
"bash_kill",
|
|
893
1036
|
"code_execution",
|
|
894
1037
|
"wait",
|
|
895
|
-
"git"
|
|
1038
|
+
"git",
|
|
1039
|
+
"ls",
|
|
1040
|
+
"stat",
|
|
1041
|
+
"wc"
|
|
896
1042
|
];
|
|
897
1043
|
/**
|
|
898
1044
|
* Typo-safe accessors for the closed builtin tool set: each key maps to the
|
|
@@ -921,13 +1067,51 @@ export const BuiltinTools = {
|
|
|
921
1067
|
bash_kill: "bash_kill",
|
|
922
1068
|
code_execution: "code_execution",
|
|
923
1069
|
wait: "wait",
|
|
924
|
-
git: "git"
|
|
1070
|
+
git: "git",
|
|
1071
|
+
ls: "ls",
|
|
1072
|
+
stat: "stat",
|
|
1073
|
+
wc: "wc"
|
|
925
1074
|
};
|
|
926
1075
|
/**
|
|
927
1076
|
* The default builtin tool set injected when `includeBuiltinTools !== false`.
|
|
928
1077
|
* It is the complete closed builtin set.
|
|
929
1078
|
*/
|
|
930
1079
|
export const DEFAULT_BUILTIN_TOOLS = BUILTIN_TOOL_NAMES;
|
|
1080
|
+
// ---------------------------------------------------------------------------
|
|
1081
|
+
// The default `skills` meta-tool
|
|
1082
|
+
// ---------------------------------------------------------------------------
|
|
1083
|
+
/**
|
|
1084
|
+
* Fixed name of the single skills meta-tool. Deliberately NOT a member of
|
|
1085
|
+
* {@link BUILTIN_TOOL_NAMES} (that closed set is the customer-cherry-pickable
|
|
1086
|
+
* toggle surface, pinned equal to `HANDS_TOOLS`) — the skills tool is IMPLIED by
|
|
1087
|
+
* a run having ≥1 skill, not chosen, and is injected platform-side. Kept in
|
|
1088
|
+
* lockstep with {@link SKILL_RESERVED_NAMES} so it can never be shadowed by a
|
|
1089
|
+
* custom tool or skill of the same name.
|
|
1090
|
+
*/
|
|
1091
|
+
export const SKILLS_TOOL_NAME = "skills";
|
|
1092
|
+
/**
|
|
1093
|
+
* The single default `skills` meta-tool (list/load) the platform injects when a
|
|
1094
|
+
* run references ≥1 workspace skill. Shared by the platform tool composer and
|
|
1095
|
+
* kept adjacent to the reserved-name guard so the model-visible contract and the
|
|
1096
|
+
* name reservation stay in one place. It replaces the former N per-skill no-arg
|
|
1097
|
+
* load-tools with one arg-taking dispatcher.
|
|
1098
|
+
*/
|
|
1099
|
+
export const SKILLS_TOOL_DEFINITION = {
|
|
1100
|
+
name: "skills",
|
|
1101
|
+
description: "List and load the workspace SKILLS available to this run. Call with {action:'list'} to see each skill's " +
|
|
1102
|
+
"name + description (cheap; do this first). Call with {action:'load', name:'<skill>'} to read that skill's " +
|
|
1103
|
+
"full SKILL.md instructions into context before doing work the skill governs. A skill's supporting files are " +
|
|
1104
|
+
"already on disk under /workspace/skills/<name>/ — load pulls the instructions; read_file/bash read the rest.",
|
|
1105
|
+
input_schema: {
|
|
1106
|
+
type: "object",
|
|
1107
|
+
properties: {
|
|
1108
|
+
action: { type: "string", enum: ["list", "load"], description: "'list' all skills, or 'load' one by name." },
|
|
1109
|
+
name: { type: "string", description: "Skill name to load (required when action='load')." }
|
|
1110
|
+
},
|
|
1111
|
+
required: ["action"],
|
|
1112
|
+
additionalProperties: false
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
931
1115
|
/**
|
|
932
1116
|
* Resolve the set of builtin tool NAMES a submission injects, deduplicated and
|
|
933
1117
|
* in {@link BUILTIN_TOOL_NAMES} order.
|
|
@@ -1159,20 +1343,20 @@ function parsePrompt(input) {
|
|
|
1159
1343
|
return parts;
|
|
1160
1344
|
}
|
|
1161
1345
|
/**
|
|
1162
|
-
* Parse the `submission.tools` union: each entry is one of
|
|
1346
|
+
* Parse the `submission.tools` union: each entry is one of TWO shapes —
|
|
1163
1347
|
* (a) a BARE STRING: a builtin tool reference (validated against
|
|
1164
1348
|
* {@link BUILTIN_TOOL_NAMES}).
|
|
1165
1349
|
* (b) `{ kind:"asset", … }`: a custom tool bundle ({@link ToolRef}).
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1169
|
-
*
|
|
1170
|
-
*
|
|
1171
|
-
*
|
|
1350
|
+
* Returns the two groups split: `tools` (custom bundles) and `builtinTools`
|
|
1351
|
+
* (the deduped builtin-name references, in {@link BUILTIN_TOOL_NAMES} order).
|
|
1352
|
+
* Tool `name`s and `assetId`s are deduped so a name/asset names at most one tool.
|
|
1353
|
+
*
|
|
1354
|
+
* Skills are NO LONGER a tool kind: a `{ kind:"skill" }` entry here is rejected
|
|
1355
|
+
* with a redirect to `submission.skills` (see {@link parseSkills}).
|
|
1172
1356
|
*/
|
|
1173
1357
|
function parseTools(input) {
|
|
1174
1358
|
if (input === undefined) {
|
|
1175
|
-
return { tools: [],
|
|
1359
|
+
return { tools: [], builtinTools: [] };
|
|
1176
1360
|
}
|
|
1177
1361
|
if (!Array.isArray(input)) {
|
|
1178
1362
|
throw new Error("submission.tools must be an array of builtin tool names or ToolRef objects");
|
|
@@ -1181,7 +1365,6 @@ function parseTools(input) {
|
|
|
1181
1365
|
const seenAssetIds = new Set();
|
|
1182
1366
|
const seenBuiltins = new Set();
|
|
1183
1367
|
const tools = [];
|
|
1184
|
-
const skillTools = [];
|
|
1185
1368
|
input.forEach((item, index) => {
|
|
1186
1369
|
const path = `submission.tools[${index}]`;
|
|
1187
1370
|
// A bare string is a builtin tool reference (e.g. BuiltinTools.web_search).
|
|
@@ -1194,41 +1377,11 @@ function parseTools(input) {
|
|
|
1194
1377
|
return;
|
|
1195
1378
|
}
|
|
1196
1379
|
const raw = requireRecord(item, path);
|
|
1197
|
-
//
|
|
1198
|
-
//
|
|
1199
|
-
//
|
|
1380
|
+
// Skills are a separate first-class input now. A skill ref must not ride the
|
|
1381
|
+
// tools union — point the caller at submission.skills instead of silently
|
|
1382
|
+
// dropping or mis-binding it.
|
|
1200
1383
|
if (raw.kind === "skill") {
|
|
1201
|
-
|
|
1202
|
-
if (key !== "kind" && key !== "assetId" && key !== "name" && key !== "description") {
|
|
1203
|
-
throw new Error(`${path}.${key} is not an allowed field for a skill tool; permitted: kind, assetId, name, description`);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
const skillFields = parseAssetRefFields({ kind: "asset", assetId: raw.assetId, name: raw.name }, path);
|
|
1207
|
-
if (!TOOL_NAME_PATTERN.test(skillFields.name)) {
|
|
1208
|
-
throw new Error(`${path}.name must match ${TOOL_NAME_PATTERN.source}`);
|
|
1209
|
-
}
|
|
1210
|
-
if (skillFields.name.includes("__")) {
|
|
1211
|
-
throw new Error(`${path}.name must not contain "__"; that separator is reserved for MCP tools`);
|
|
1212
|
-
}
|
|
1213
|
-
if (seenNames.has(skillFields.name)) {
|
|
1214
|
-
throw new Error(`submission.tools duplicate name: ${skillFields.name}`);
|
|
1215
|
-
}
|
|
1216
|
-
seenNames.add(skillFields.name);
|
|
1217
|
-
if (seenAssetIds.has(skillFields.assetId)) {
|
|
1218
|
-
throw new Error(`submission.tools duplicate assetId: ${skillFields.assetId}`);
|
|
1219
|
-
}
|
|
1220
|
-
seenAssetIds.add(skillFields.assetId);
|
|
1221
|
-
const skillDescription = requireString(raw.description, `${path}.description`);
|
|
1222
|
-
if (skillDescription.trim().length === 0 || skillDescription.length > 2048) {
|
|
1223
|
-
throw new Error(`${path}.description must be non-empty and <= 2048 chars`);
|
|
1224
|
-
}
|
|
1225
|
-
skillTools.push({
|
|
1226
|
-
kind: "skill",
|
|
1227
|
-
assetId: skillFields.assetId,
|
|
1228
|
-
name: skillFields.name,
|
|
1229
|
-
description: skillDescription
|
|
1230
|
-
});
|
|
1231
|
-
return;
|
|
1384
|
+
throw new Error(`${path} is a skill ref; skills go in submission.skills (by name), not submission.tools`);
|
|
1232
1385
|
}
|
|
1233
1386
|
for (const key of Object.keys(raw)) {
|
|
1234
1387
|
if (key !== "kind" &&
|
|
@@ -1280,7 +1433,118 @@ function parseTools(input) {
|
|
|
1280
1433
|
});
|
|
1281
1434
|
});
|
|
1282
1435
|
const builtinTools = BUILTIN_TOOL_NAMES.filter((name) => seenBuiltins.has(name));
|
|
1283
|
-
return { tools,
|
|
1436
|
+
return { tools, builtinTools };
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Upper bound on the number of workspace skills a single run may reference.
|
|
1440
|
+
* A run's skill list is a discovery surface, not a bulk-mount channel; 64 is
|
|
1441
|
+
* generous headroom over any realistic per-run set while capping the meta-tool
|
|
1442
|
+
* `list` payload and the submit-time resolution fan-out.
|
|
1443
|
+
*/
|
|
1444
|
+
export const SKILLS_MAX = 64;
|
|
1445
|
+
/**
|
|
1446
|
+
* Parse `submission.skills`: an array of PUBLIC by-name skill refs
|
|
1447
|
+
* (`{ kind:"skill", name }`). Validates each `name` against
|
|
1448
|
+
* {@link SKILL_NAME_PATTERN}, rejects the `__` MCP separator and the reserved
|
|
1449
|
+
* skills names, dedups by name, and bounds the list at {@link SKILLS_MAX}.
|
|
1450
|
+
*/
|
|
1451
|
+
export function parseSkills(input) {
|
|
1452
|
+
if (input === undefined || input === null) {
|
|
1453
|
+
return [];
|
|
1454
|
+
}
|
|
1455
|
+
if (!Array.isArray(input)) {
|
|
1456
|
+
throw new Error("submission.skills must be an array of { kind:'skill', name } refs");
|
|
1457
|
+
}
|
|
1458
|
+
if (input.length > SKILLS_MAX) {
|
|
1459
|
+
throw new Error(`submission.skills exceeds the ${SKILLS_MAX}-skill limit (got ${input.length})`);
|
|
1460
|
+
}
|
|
1461
|
+
const seen = new Set();
|
|
1462
|
+
return input.map((item, index) => {
|
|
1463
|
+
const path = `submission.skills[${index}]`;
|
|
1464
|
+
const raw = requireRecord(item, path);
|
|
1465
|
+
for (const key of Object.keys(raw)) {
|
|
1466
|
+
if (key !== "kind" && key !== "name") {
|
|
1467
|
+
throw new Error(`${path}.${key} is not an allowed field for a skill ref; permitted: kind, name`);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
if (raw.kind !== "skill") {
|
|
1471
|
+
throw new Error(`${path}.kind must be 'skill' (got ${JSON.stringify(raw.kind)})`);
|
|
1472
|
+
}
|
|
1473
|
+
const name = requireString(raw.name, `${path}.name`);
|
|
1474
|
+
assertValidSkillName(name, `${path}.name`);
|
|
1475
|
+
if (seen.has(name)) {
|
|
1476
|
+
throw new Error(`submission.skills duplicate name: ${name}`);
|
|
1477
|
+
}
|
|
1478
|
+
seen.add(name);
|
|
1479
|
+
return { kind: "skill", name };
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Parse `submission.resolvedSkills`: the BOOT-RECORD-ONLY resolved refs
|
|
1484
|
+
* (`{ kind:"skill", assetId, name, description }`) the BFF resolver writes.
|
|
1485
|
+
* REJECTED on the public ingress path (`trusted === false`) so a caller can
|
|
1486
|
+
* never inject an asset + description; only the in-container trusted re-parse
|
|
1487
|
+
* (`trustedReparse: true`) accepts them.
|
|
1488
|
+
*/
|
|
1489
|
+
function parseResolvedSkills(input, trusted) {
|
|
1490
|
+
if (input === undefined || input === null) {
|
|
1491
|
+
return [];
|
|
1492
|
+
}
|
|
1493
|
+
if (!trusted) {
|
|
1494
|
+
throw new Error("submission.resolvedSkills is a platform-internal (boot-record) field and may not be set by callers; " +
|
|
1495
|
+
"reference workspace skills by name via submission.skills");
|
|
1496
|
+
}
|
|
1497
|
+
if (!Array.isArray(input)) {
|
|
1498
|
+
throw new Error("submission.resolvedSkills must be an array of resolved skill refs");
|
|
1499
|
+
}
|
|
1500
|
+
if (input.length > SKILLS_MAX) {
|
|
1501
|
+
throw new Error(`submission.resolvedSkills exceeds the ${SKILLS_MAX}-skill limit (got ${input.length})`);
|
|
1502
|
+
}
|
|
1503
|
+
const seenNames = new Set();
|
|
1504
|
+
const seenAssetIds = new Set();
|
|
1505
|
+
return input.map((item, index) => {
|
|
1506
|
+
const path = `submission.resolvedSkills[${index}]`;
|
|
1507
|
+
const raw = requireRecord(item, path);
|
|
1508
|
+
for (const key of Object.keys(raw)) {
|
|
1509
|
+
if (key !== "kind" && key !== "assetId" && key !== "name" && key !== "description") {
|
|
1510
|
+
throw new Error(`${path}.${key} is not an allowed field for a resolved skill ref; permitted: kind, assetId, name, description`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (raw.kind !== "skill") {
|
|
1514
|
+
throw new Error(`${path}.kind must be 'skill' (got ${JSON.stringify(raw.kind)})`);
|
|
1515
|
+
}
|
|
1516
|
+
const fields = parseAssetRefFields({ kind: "asset", assetId: raw.assetId, name: raw.name }, path);
|
|
1517
|
+
assertValidSkillName(fields.name, `${path}.name`);
|
|
1518
|
+
if (seenNames.has(fields.name)) {
|
|
1519
|
+
throw new Error(`submission.resolvedSkills duplicate name: ${fields.name}`);
|
|
1520
|
+
}
|
|
1521
|
+
seenNames.add(fields.name);
|
|
1522
|
+
if (seenAssetIds.has(fields.assetId)) {
|
|
1523
|
+
throw new Error(`submission.resolvedSkills duplicate assetId: ${fields.assetId}`);
|
|
1524
|
+
}
|
|
1525
|
+
seenAssetIds.add(fields.assetId);
|
|
1526
|
+
const description = requireString(raw.description, `${path}.description`);
|
|
1527
|
+
if (description.trim().length === 0 || description.length > 2048) {
|
|
1528
|
+
throw new Error(`${path}.description must be non-empty and <= 2048 chars`);
|
|
1529
|
+
}
|
|
1530
|
+
return { kind: "skill", assetId: fields.assetId, name: fields.name, description };
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Shared skill-name gate for {@link parseSkills} / {@link parseResolvedSkills}
|
|
1535
|
+
* (and mirrored SDK-side in `Skill`): pattern + `__` MCP separator + reserved
|
|
1536
|
+
* names (`skills`, `skill`).
|
|
1537
|
+
*/
|
|
1538
|
+
function assertValidSkillName(name, field) {
|
|
1539
|
+
if (!SKILL_NAME_PATTERN.test(name)) {
|
|
1540
|
+
throw new Error(`${field} must match ${SKILL_NAME_PATTERN.source}`);
|
|
1541
|
+
}
|
|
1542
|
+
if (name.includes("__")) {
|
|
1543
|
+
throw new Error(`${field} must not contain "__"; that separator is reserved for MCP tools`);
|
|
1544
|
+
}
|
|
1545
|
+
if (SKILL_RESERVED_NAMES.has(name)) {
|
|
1546
|
+
throw new Error(`${field} must not be a reserved skills name (${[...SKILL_RESERVED_NAMES].join(", ")})`);
|
|
1547
|
+
}
|
|
1284
1548
|
}
|
|
1285
1549
|
function parseAgentsMd(input) {
|
|
1286
1550
|
if (input === undefined)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared "did you mean?" suggester — the SSoT for near-miss enum hints across
|
|
3
|
+
* the SDK and the CLI. Previously this lived only in the CLI, so the SDK could
|
|
4
|
+
* not emit a fuzzy model/provider hint even though the docs promised one.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The closest candidate to `input` by a unique case-insensitive prefix match,
|
|
8
|
+
* else by Levenshtein distance (≤ 2), else `undefined`. Ties break by
|
|
9
|
+
* declaration order. Used on an invalid `--model` / `--provider` /
|
|
10
|
+
* `--runtime-size` (and the SDK's unknown-model throw) to turn a flat rejection
|
|
11
|
+
* into a fix hint.
|
|
12
|
+
*/
|
|
13
|
+
export declare function suggest(input: string, candidates: readonly string[]): string | undefined;
|
|
14
|
+
/** Levenshtein edit distance between two strings (rolling two-row DP). */
|
|
15
|
+
export declare function levenshtein(a: string, b: string): number;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared "did you mean?" suggester — the SSoT for near-miss enum hints across
|
|
3
|
+
* the SDK and the CLI. Previously this lived only in the CLI, so the SDK could
|
|
4
|
+
* not emit a fuzzy model/provider hint even though the docs promised one.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The closest candidate to `input` by a unique case-insensitive prefix match,
|
|
8
|
+
* else by Levenshtein distance (≤ 2), else `undefined`. Ties break by
|
|
9
|
+
* declaration order. Used on an invalid `--model` / `--provider` /
|
|
10
|
+
* `--runtime-size` (and the SDK's unknown-model throw) to turn a flat rejection
|
|
11
|
+
* into a fix hint.
|
|
12
|
+
*/
|
|
13
|
+
export function suggest(input, candidates) {
|
|
14
|
+
const needle = input.trim();
|
|
15
|
+
if (!needle)
|
|
16
|
+
return undefined;
|
|
17
|
+
// Unique case-insensitive prefix match first (cheap + intuitive).
|
|
18
|
+
const lower = needle.toLowerCase();
|
|
19
|
+
const prefixHits = candidates.filter((c) => c.toLowerCase().startsWith(lower));
|
|
20
|
+
if (prefixHits.length === 1)
|
|
21
|
+
return prefixHits[0];
|
|
22
|
+
// Else nearest by edit distance, ties broken by declaration order.
|
|
23
|
+
let best;
|
|
24
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
25
|
+
for (const candidate of candidates) {
|
|
26
|
+
const dist = levenshtein(needle, candidate);
|
|
27
|
+
if (dist < bestDist) {
|
|
28
|
+
bestDist = dist;
|
|
29
|
+
best = candidate;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return bestDist <= 2 ? best : undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Levenshtein edit distance between two strings (rolling two-row DP). */
|
|
35
|
+
export function levenshtein(a, b) {
|
|
36
|
+
const m = a.length;
|
|
37
|
+
const n = b.length;
|
|
38
|
+
if (m === 0)
|
|
39
|
+
return n;
|
|
40
|
+
if (n === 0)
|
|
41
|
+
return m;
|
|
42
|
+
let prev = Array.from({ length: n + 1 }, (_, j) => j);
|
|
43
|
+
let curr = new Array(n + 1);
|
|
44
|
+
for (let i = 1; i <= m; i++) {
|
|
45
|
+
curr[0] = i;
|
|
46
|
+
for (let j = 1; j <= n; j++) {
|
|
47
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
48
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
49
|
+
}
|
|
50
|
+
[prev, curr] = [curr, prev];
|
|
51
|
+
}
|
|
52
|
+
return prev[n];
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=suggest.js.map
|
package/dist/asset-upload.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Asset materialization for the SDK run / session path.
|
|
3
3
|
*
|
|
4
|
-
* Every inline
|
|
4
|
+
* Every inline `Skill` / `Tool` / `AgentsMd` / `File` draft is materialized
|
|
5
5
|
* to the hosted API's content-addressable asset store before the session
|
|
6
6
|
* starts, so the wire submission carries only storage-neutral refs.
|
|
7
7
|
*
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* 3. POST /assets/finalize → confirms the object exists (HEAD only).
|
|
20
20
|
*
|
|
21
21
|
*/
|
|
22
|
+
import type { ByteSink } from "./canonical-zip.js";
|
|
22
23
|
/**
|
|
23
24
|
* Subset of `HttpClient` needed by the asset uploader. Defined as a
|
|
24
25
|
* structural type so tests can supply a thin stub without dragging in
|
|
@@ -32,6 +33,10 @@ export type AssetFetch = (input: string, init?: RequestInit) => Promise<{
|
|
|
32
33
|
readonly ok: boolean;
|
|
33
34
|
readonly status: number;
|
|
34
35
|
text(): Promise<string>;
|
|
36
|
+
/** Present on real `fetch` Responses; used to read a multipart part's ETag. */
|
|
37
|
+
readonly headers?: {
|
|
38
|
+
get(name: string): string | null;
|
|
39
|
+
};
|
|
35
40
|
}>;
|
|
36
41
|
export interface UploadAssetArgs {
|
|
37
42
|
readonly http: AssetsHttpClient;
|
|
@@ -58,3 +63,24 @@ export interface UploadedAsset {
|
|
|
58
63
|
* and the runner re-checks on download.
|
|
59
64
|
*/
|
|
60
65
|
export declare function uploadAsset(args: UploadAssetArgs): Promise<UploadedAsset>;
|
|
66
|
+
/** Default multipart part size — 16 MiB covers the 100 GiB cap within S3's 10 000-part limit. */
|
|
67
|
+
export declare const DEFAULT_MULTIPART_PART_SIZE: number;
|
|
68
|
+
/** Default in-flight part concurrency. */
|
|
69
|
+
export declare const DEFAULT_MULTIPART_CONCURRENCY = 4;
|
|
70
|
+
/**
|
|
71
|
+
* Drives the canonical-zip framer, pushing every zip byte into `sink` in order.
|
|
72
|
+
* Called TWICE (pass 1 hash, pass 2 upload); the framer is deterministic so both
|
|
73
|
+
* passes emit byte-identical streams.
|
|
74
|
+
*/
|
|
75
|
+
export type ZipStreamDriver = (sink: ByteSink) => Promise<void>;
|
|
76
|
+
export interface UploadAssetStreamArgs {
|
|
77
|
+
readonly http: AssetsHttpClient;
|
|
78
|
+
/** Re-openable, deterministic canonical-zip stream driver (pass-1 hash == pass-2 bytes). */
|
|
79
|
+
readonly drive: ZipStreamDriver;
|
|
80
|
+
readonly contentType?: string;
|
|
81
|
+
readonly fetch?: AssetFetch;
|
|
82
|
+
readonly partSize?: number;
|
|
83
|
+
readonly partConcurrency?: number;
|
|
84
|
+
}
|
|
85
|
+
/** Stream a large asset to the content store via the two-pass multipart flow. */
|
|
86
|
+
export declare function uploadAssetMultipart(args: UploadAssetStreamArgs): Promise<UploadedAsset>;
|