@harperfast/harper 5.2.0 → 5.2.2
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/bin/copyDb.ts +40 -11
- package/bin/harper.ts +20 -51
- package/bin/help.ts +216 -0
- package/components/Application.ts +236 -46
- package/components/ApplicationScope.ts +26 -0
- package/components/EntryHandler.ts +410 -105
- package/components/RuntimeModuleTracker.ts +189 -0
- package/components/Scope.ts +68 -24
- package/components/componentLoader.ts +70 -16
- package/components/deployLifecycle.ts +119 -33
- package/components/deploymentRecorder.ts +102 -11
- package/components/mcp/tools/operations.ts +6 -0
- package/components/operations.js +8 -6
- package/config/configUtils.ts +78 -20
- package/config/harperConfigEnvVars.ts +114 -17
- package/config-root.schema.json +10 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +26 -5
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -4
- package/dataLayer/hdbInfoController.ts +8 -0
- package/dataLayer/schemaDescribe.ts +2 -1
- package/dist/bin/copyDb.js +33 -9
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/harper.d.ts +6 -0
- package/dist/bin/harper.js +18 -50
- package/dist/bin/harper.js.map +1 -1
- package/dist/bin/help.d.ts +8 -0
- package/dist/bin/help.js +192 -0
- package/dist/bin/help.js.map +1 -0
- package/dist/components/Application.d.ts +16 -1
- package/dist/components/Application.js +210 -38
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +7 -0
- package/dist/components/ApplicationScope.js +22 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/EntryHandler.d.ts +4 -4
- package/dist/components/EntryHandler.js +386 -95
- package/dist/components/EntryHandler.js.map +1 -1
- package/dist/components/RuntimeModuleTracker.d.ts +11 -0
- package/dist/components/RuntimeModuleTracker.js +189 -0
- package/dist/components/RuntimeModuleTracker.js.map +1 -0
- package/dist/components/Scope.d.ts +1 -0
- package/dist/components/Scope.js +69 -22
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +69 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/deployLifecycle.d.ts +6 -2
- package/dist/components/deployLifecycle.js +109 -31
- package/dist/components/deployLifecycle.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +6 -0
- package/dist/components/deploymentRecorder.js +94 -12
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/tools/operations.js +6 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/operations.js +8 -6
- package/dist/components/operations.js.map +1 -1
- package/dist/config/configUtils.d.ts +6 -0
- package/dist/config/configUtils.js +78 -18
- package/dist/config/configUtils.js.map +1 -1
- package/dist/config/harperConfigEnvVars.js +108 -16
- package/dist/config/harperConfigEnvVars.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +12 -5
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -4
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
- package/dist/dataLayer/hdbInfoController.js +4 -0
- package/dist/dataLayer/hdbInfoController.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/resources/DatabaseTransaction.d.ts +57 -0
- package/dist/resources/DatabaseTransaction.js +284 -104
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +8 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js +22 -4
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +8 -6
- package/dist/resources/Table.js +158 -31
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +6 -6
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.js +27 -1
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/blob.d.ts +0 -1
- package/dist/resources/blob.js +15 -7
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/databases.d.ts +22 -5
- package/dist/resources/databases.js +118 -9
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +15 -2
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +128 -39
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/jsResource.d.ts +4 -26
- package/dist/resources/jsResource.js +5 -59
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/models/Models.d.ts +11 -1
- package/dist/resources/models/Models.js +10 -1
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +9 -0
- package/dist/resources/models/backendRegistry.js +10 -0
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +16 -1
- package/dist/resources/models/openaiStream.js +113 -21
- package/dist/resources/models/openaiStream.js.map +1 -1
- package/dist/resources/models/v1/chatCompletions.d.ts +54 -0
- package/dist/resources/models/v1/chatCompletions.js +115 -0
- package/dist/resources/models/v1/chatCompletions.js.map +1 -0
- package/dist/resources/models/v1/embeddings.d.ts +11 -0
- package/dist/resources/models/v1/embeddings.js +71 -0
- package/dist/resources/models/v1/embeddings.js.map +1 -0
- package/dist/resources/models/v1/errors.d.ts +54 -0
- package/dist/resources/models/v1/errors.js +130 -0
- package/dist/resources/models/v1/errors.js.map +1 -0
- package/dist/resources/models/v1/index.d.ts +36 -0
- package/dist/resources/models/v1/index.js +75 -0
- package/dist/resources/models/v1/index.js.map +1 -0
- package/dist/resources/models/v1/models.d.ts +26 -0
- package/dist/resources/models/v1/models.js +44 -0
- package/dist/resources/models/v1/models.js.map +1 -0
- package/dist/resources/models/v1/translation.d.ts +133 -0
- package/dist/resources/models/v1/translation.js +298 -0
- package/dist/resources/models/v1/translation.js.map +1 -0
- package/dist/resources/roles.d.ts +1 -1
- package/dist/resources/roles.js +54 -7
- package/dist/resources/roles.js.map +1 -1
- package/dist/resources/search.d.ts +1 -1
- package/dist/resources/search.js +7 -3
- package/dist/resources/search.js.map +1 -1
- package/dist/security/jsLoader.js +84 -33
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/role.js +4 -0
- package/dist/security/role.js.map +1 -1
- package/dist/security/superUserGuard.d.ts +7 -0
- package/dist/security/superUserGuard.js +23 -0
- package/dist/security/superUserGuard.js.map +1 -0
- package/dist/security/tokenAuthentication.d.ts +0 -1
- package/dist/security/tokenAuthentication.js +6 -2
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/security/user.d.ts +6 -1
- package/dist/security/user.js +23 -1
- package/dist/security/user.js.map +1 -1
- package/dist/server/http.d.ts +23 -2
- package/dist/server/http.js +99 -12
- package/dist/server/http.js.map +1 -1
- package/dist/server/operationsServer.d.ts +0 -2
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/multipartParser.js +9 -0
- package/dist/server/serverHelpers/multipartParser.js.map +1 -1
- package/dist/server/serverHelpers/operationAuthorizationState.d.ts +2 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js +13 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -0
- package/dist/server/serverHelpers/registeredOperations.d.ts +3 -2
- package/dist/server/serverHelpers/registeredOperations.js +14 -13
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +19 -3
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -1
- package/dist/server/serverHelpers/serverUtilities.js +10 -7
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/static.js +57 -18
- package/dist/server/static.js.map +1 -1
- package/dist/server/storageReclamation.d.ts +17 -0
- package/dist/server/storageReclamation.js +90 -6
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/manageThreads.js +8 -0
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.js +20 -0
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +12 -0
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/sqlEngine/diff/differential.js +7 -3
- package/dist/sqlEngine/diff/differential.js.map +1 -1
- package/dist/sqlEngine/parser/normalizer.js +37 -9
- package/dist/sqlEngine/parser/normalizer.js.map +1 -1
- package/dist/sqlTranslator/index.js +6 -1
- package/dist/sqlTranslator/index.js.map +1 -1
- package/dist/upgrade/upgradePrompt.d.ts +2 -2
- package/dist/upgrade/upgradePrompt.js +22 -3
- package/dist/upgrade/upgradePrompt.js.map +1 -1
- package/dist/utility/environment/environmentManager.d.ts +11 -0
- package/dist/utility/environment/environmentManager.js +154 -0
- package/dist/utility/environment/environmentManager.js.map +1 -1
- package/dist/utility/errors/commonErrors.d.ts +1 -0
- package/dist/utility/errors/commonErrors.js +1 -0
- package/dist/utility/errors/commonErrors.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +2 -0
- package/dist/utility/hdbTerms.js +2 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/install/installer.js +32 -0
- package/dist/utility/install/installer.js.map +1 -1
- package/dist/utility/lmdb/environmentUtility.js +2 -1
- package/dist/utility/lmdb/environmentUtility.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +7 -0
- package/dist/utility/logging/harper_logger.js +57 -24
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/npm-shrinkwrap.json +208 -198
- package/package.json +10 -8
- package/resources/DESIGN.md +1 -0
- package/resources/DatabaseTransaction.ts +313 -102
- package/resources/ResourceInterface.ts +8 -2
- package/resources/Resources.ts +22 -4
- package/resources/Table.ts +398 -259
- package/resources/analytics/write.ts +22 -20
- package/resources/auditStore.ts +32 -1
- package/resources/blob.ts +15 -8
- package/resources/databases.ts +123 -12
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +119 -45
- package/resources/jsResource.ts +5 -62
- package/resources/models/Models.ts +14 -1
- package/resources/models/backendRegistry.ts +10 -0
- package/resources/models/openaiStream.ts +131 -19
- package/resources/models/v1/chatCompletions.ts +128 -0
- package/resources/models/v1/embeddings.ts +70 -0
- package/resources/models/v1/errors.ts +141 -0
- package/resources/models/v1/index.ts +72 -0
- package/resources/models/v1/models.ts +53 -0
- package/resources/models/v1/translation.ts +362 -0
- package/resources/roles.ts +67 -7
- package/resources/search.ts +8 -3
- package/security/jsLoader.ts +84 -30
- package/security/role.ts +7 -0
- package/security/superUserGuard.ts +20 -0
- package/security/tokenAuthentication.ts +6 -3
- package/security/user.ts +26 -1
- package/server/DESIGN.md +45 -34
- package/server/http.ts +100 -13
- package/server/operationsServer.ts +0 -2
- package/server/serverHelpers/multipartParser.ts +9 -0
- package/server/serverHelpers/operationAuthorizationState.ts +11 -0
- package/server/serverHelpers/registeredOperations.ts +19 -15
- package/server/serverHelpers/serverHandlers.js +20 -3
- package/server/serverHelpers/serverUtilities.ts +10 -7
- package/server/static.ts +75 -20
- package/server/storageReclamation.ts +104 -8
- package/server/threads/manageThreads.js +8 -0
- package/server/threads/socketRouter.ts +20 -0
- package/server/threads/threadServer.js +11 -0
- package/sqlTranslator/index.ts +6 -1
- package/static/defaultConfig.yaml +2 -0
- package/studio/web/assets/{Chat-DoVWScmq.js → Chat-BMUjPklt.js} +5 -5
- package/studio/web/assets/{Chat-DoVWScmq.js.map → Chat-BMUjPklt.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js → FloatingChat-Cbghuxe5.js} +5 -5
- package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js.map → FloatingChat-Cbghuxe5.js.map} +1 -1
- package/studio/web/assets/{apiToken-BUI_04o7.js → apiToken-CkgTQAqc.js} +2 -2
- package/studio/web/assets/{apiToken-BUI_04o7.js.map → apiToken-CkgTQAqc.js.map} +1 -1
- package/studio/web/assets/{applications-D03NA7wW.js → applications-WvF4NOTN.js} +13 -13
- package/studio/web/assets/applications-WvF4NOTN.js.map +1 -0
- package/studio/web/assets/chevron-right-anDJCGlp.js +2 -0
- package/studio/web/assets/chevron-right-anDJCGlp.js.map +1 -0
- package/studio/web/assets/index-CDeqVx71.css +1 -0
- package/studio/web/assets/index-Sevt3MVx.js +819 -0
- package/studio/web/assets/index-Sevt3MVx.js.map +1 -0
- package/studio/web/assets/index.lazy-Dg5YYPGg.js +14 -0
- package/studio/web/assets/{index.lazy-Dx3MpyDC.js.map → index.lazy-Dg5YYPGg.js.map} +1 -1
- package/studio/web/assets/{jsonMode-Cjj7dtlr.js → jsonMode-CBSEleE7.js} +2 -2
- package/studio/web/assets/{jsonMode-Cjj7dtlr.js.map → jsonMode-CBSEleE7.js.map} +1 -1
- package/studio/web/assets/languageServices-gxedeeTo.js +2 -0
- package/studio/web/assets/languageServices-gxedeeTo.js.map +1 -0
- package/studio/web/assets/{notifications-0edoFTsb.js → notifications-Ap84YyQp.js} +2 -2
- package/studio/web/assets/{notifications-0edoFTsb.js.map → notifications-Ap84YyQp.js.map} +1 -1
- package/studio/web/assets/notifications-vehqyOLM.js +2 -0
- package/studio/web/assets/{notifications-CwKhipK7.js.map → notifications-vehqyOLM.js.map} +1 -1
- package/studio/web/assets/{pollUnlessForbidden-HHdQZW1N.js → pollUnlessForbidden-BhzefVRn.js} +2 -2
- package/studio/web/assets/pollUnlessForbidden-BhzefVRn.js.map +1 -0
- package/studio/web/assets/{profile-DUfEPQtx.js → profile-DzDpRLHY.js} +2 -2
- package/studio/web/assets/{profile-DUfEPQtx.js.map → profile-DzDpRLHY.js.map} +1 -1
- package/studio/web/assets/queryClient-CgtkoiCY.js +2 -0
- package/studio/web/assets/queryClient-CgtkoiCY.js.map +1 -0
- package/studio/web/assets/regions-DpJ8s7iN.js +2 -0
- package/studio/web/assets/regions-DpJ8s7iN.js.map +1 -0
- package/studio/web/assets/register-BDy01-q3.js +3 -0
- package/studio/web/assets/register-BDy01-q3.js.map +1 -0
- package/studio/web/assets/register-CQHRwNP4.js +3 -0
- package/studio/web/assets/register-CQHRwNP4.js.map +1 -0
- package/studio/web/assets/{setComponentFile-DMPo4UjC.js → setComponentFile-D0aPcI3W.js} +2 -2
- package/studio/web/assets/{setComponentFile-DMPo4UjC.js.map → setComponentFile-D0aPcI3W.js.map} +1 -1
- package/studio/web/assets/setup-I15FAF9H.js +3 -0
- package/studio/web/assets/{setup-B56Oz1_u.js.map → setup-I15FAF9H.js.map} +1 -1
- package/studio/web/assets/status-BF1eJshJ.js +62 -0
- package/studio/web/assets/status-BF1eJshJ.js.map +1 -0
- package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js → swagger-ui-react-DwSPXXhl.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js.map → swagger-ui-react-DwSPXXhl.js.map} +1 -1
- package/studio/web/assets/{textarea-DhLMZ3PA.js → table-DpOjNQHt.js} +2 -10
- package/studio/web/assets/table-DpOjNQHt.js.map +1 -0
- package/studio/web/assets/{tsMode-CrHCRjTK.js → tsMode-Bu51A-VG.js} +2 -2
- package/studio/web/assets/{tsMode-CrHCRjTK.js.map → tsMode-Bu51A-VG.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js → useEntityRestURL-DrV1zMrN.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js.map → useEntityRestURL-DrV1zMrN.js.map} +1 -1
- package/studio/web/index.html +6 -7
- package/upgrade/upgradePrompt.ts +22 -3
- package/utility/environment/environmentManager.ts +152 -0
- package/utility/errors/commonErrors.ts +2 -0
- package/utility/hdbTerms.ts +2 -0
- package/utility/install/installer.ts +37 -0
- package/utility/lmdb/environmentUtility.ts +2 -1
- package/utility/logging/harper_logger.ts +57 -26
- package/studio/web/assets/applications-D03NA7wW.js.map +0 -1
- package/studio/web/assets/index-Bh_CNAHr.js +0 -809
- package/studio/web/assets/index-Bh_CNAHr.js.map +0 -1
- package/studio/web/assets/index-C8Mv9kHV.css +0 -1
- package/studio/web/assets/index.lazy-Dx3MpyDC.js +0 -14
- package/studio/web/assets/notifications-CwKhipK7.js +0 -2
- package/studio/web/assets/pollUnlessForbidden-HHdQZW1N.js.map +0 -1
- package/studio/web/assets/queryClient-CbA8wM7J.js +0 -2
- package/studio/web/assets/queryClient-CbA8wM7J.js.map +0 -1
- package/studio/web/assets/setup-B56Oz1_u.js +0 -3
- package/studio/web/assets/status-BAod7p3o.js +0 -62
- package/studio/web/assets/status-BAod7p3o.js.map +0 -1
- package/studio/web/assets/textarea-DhLMZ3PA.js.map +0 -1
|
@@ -14,6 +14,51 @@
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.openaiStream = openaiStream;
|
|
16
16
|
const node_crypto_1 = require("node:crypto");
|
|
17
|
+
// Bounds on per-stream tool-call assembly. The backend supplies both the call ids and the
|
|
18
|
+
// argument fields, and this runs on a public HTTP path, so neither can be unbounded. Overflow
|
|
19
|
+
// terminates the stream through the same sanitized error-frame path as any backend failure.
|
|
20
|
+
const MAX_TOOL_CALLS_PER_STREAM = 256;
|
|
21
|
+
const MAX_TOOL_ARGUMENT_KEYS = 1024;
|
|
22
|
+
// Cumulative serialized-character budget across the WHOLE stream's assembly (ids, names,
|
|
23
|
+
// and every argument value as it arrives, plus per-entry JSON syntax so the count is an
|
|
24
|
+
// upper bound on `JSON.stringify(arguments)`, not just the raw content). Call/key counts
|
|
25
|
+
// alone don't bound memory — one key can hold an arbitrarily large value, and the final
|
|
26
|
+
// JSON.stringify duplicates the retained allocation — so the budget is charged per delta
|
|
27
|
+
// (O(delta), no re-serialization of the accumulator) and monotonically: replacing an
|
|
28
|
+
// existing key charges the new value too, so churn cannot smuggle unbounded values under
|
|
29
|
+
// a stable key count. The SSE frame that flushes the calls adds only a bounded constant
|
|
30
|
+
// envelope per call plus string-escaping of the arguments blob (< 2x), so the frame size
|
|
31
|
+
// is bounded by a small multiple of this budget.
|
|
32
|
+
const MAX_TOOL_ASSEMBLY_CHARS = 1_048_576;
|
|
33
|
+
/** Signals that a stream exceeded the tool-assembly bounds; surfaced as an SSE error frame. */
|
|
34
|
+
class ToolAssemblyOverflowError extends Error {
|
|
35
|
+
statusCode = 502;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Merge `source` into `target`, returning the keys added and the serialized characters
|
|
39
|
+
* charged. Charging over-approximates `JSON.stringify(target).length`: each first add
|
|
40
|
+
* charges the key plus 4 chars of JSON syntax (`"key":` quotes and colon, plus the
|
|
41
|
+
* comma/brace share), and every assignment — replacements included — charges the
|
|
42
|
+
* serialized value. Since a replacement's earlier charge is never refunded, the
|
|
43
|
+
* cumulative total stays an upper bound on the retained serialization while keeping
|
|
44
|
+
* accumulation O(delta), not O(total).
|
|
45
|
+
*/
|
|
46
|
+
function assignCountingNewKeys(target, source) {
|
|
47
|
+
let addedKeys = 0;
|
|
48
|
+
let addedChars = 0;
|
|
49
|
+
for (const key in source) {
|
|
50
|
+
if (!(key in target)) {
|
|
51
|
+
addedKeys++;
|
|
52
|
+
addedChars += key.length + 4;
|
|
53
|
+
}
|
|
54
|
+
const value = source[key];
|
|
55
|
+
// `?? ''`: JSON.stringify returns undefined for undefined/function/symbol values —
|
|
56
|
+
// impossible from JSON.parse but reachable from a custom backend's crafted object.
|
|
57
|
+
addedChars += (JSON.stringify(value) ?? '').length;
|
|
58
|
+
target[key] = value;
|
|
59
|
+
}
|
|
60
|
+
return { addedKeys, addedChars };
|
|
61
|
+
}
|
|
17
62
|
/**
|
|
18
63
|
* Wrap a `GenerateChunk` async iterable as OpenAI `chat.completion.chunk` SSE messages,
|
|
19
64
|
* terminated by the `[DONE]` sentinel. Content deltas stream inline; tool calls are
|
|
@@ -32,6 +77,7 @@ async function* openaiStream(tokens, opts = {}) {
|
|
|
32
77
|
// (`{"a":1}` + `{"b":2}` → invalid JSON) — Harper's already-buffered upstream model
|
|
33
78
|
// means we cannot faithfully reproduce per-token argument fragments anyway.
|
|
34
79
|
const toolAssembly = new Map();
|
|
80
|
+
let assemblyChars = 0;
|
|
35
81
|
const chunk = (delta, finish) => ({
|
|
36
82
|
data: {
|
|
37
83
|
id,
|
|
@@ -41,30 +87,76 @@ async function* openaiStream(tokens, opts = {}) {
|
|
|
41
87
|
choices: [{ index: 0, delta, finish_reason: finish }],
|
|
42
88
|
},
|
|
43
89
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
90
|
+
try {
|
|
91
|
+
for await (const token of tokens) {
|
|
92
|
+
if (token.deltaContent !== undefined) {
|
|
93
|
+
const delta = {};
|
|
94
|
+
if (!roleSent) {
|
|
95
|
+
delta.role = 'assistant';
|
|
96
|
+
roleSent = true;
|
|
97
|
+
}
|
|
98
|
+
delta.content = token.deltaContent;
|
|
99
|
+
yield chunk(delta, null);
|
|
50
100
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
101
|
+
if (token.deltaToolCalls) {
|
|
102
|
+
for (const incoming of token.deltaToolCalls) {
|
|
103
|
+
if (!incoming.id)
|
|
104
|
+
continue;
|
|
105
|
+
let existing = toolAssembly.get(incoming.id);
|
|
106
|
+
if (!existing) {
|
|
107
|
+
// Cap distinct calls per stream: ids come from the backend, and an
|
|
108
|
+
// unbounded map on a public HTTP path is a memory risk.
|
|
109
|
+
if (toolAssembly.size >= MAX_TOOL_CALLS_PER_STREAM) {
|
|
110
|
+
throw new ToolAssemblyOverflowError(`stream exceeded ${MAX_TOOL_CALLS_PER_STREAM} tool calls`);
|
|
111
|
+
}
|
|
112
|
+
// Null-prototype: arguments come from JSON.parse, so a field literally
|
|
113
|
+
// named `__proto__` is an own property. Object.assign uses [[Set]], which
|
|
114
|
+
// on an ordinary object would hit Object.prototype's inherited `__proto__`
|
|
115
|
+
// setter and silently drop the field (the previous spread did not).
|
|
116
|
+
existing = { index: toolAssembly.size, arguments: Object.create(null), argumentCount: 0 };
|
|
117
|
+
toolAssembly.set(incoming.id, existing);
|
|
118
|
+
// + 96: the flush frame's fixed per-call envelope (index/id/type/function
|
|
119
|
+
// syntax and the argument object's braces), so 256 calls of envelope are
|
|
120
|
+
// inside the budget too, not on top of it.
|
|
121
|
+
assemblyChars += incoming.id.length + 96;
|
|
122
|
+
}
|
|
123
|
+
if (incoming.name && incoming.name !== existing.name) {
|
|
124
|
+
assemblyChars += incoming.name.length;
|
|
125
|
+
existing.name = incoming.name;
|
|
126
|
+
}
|
|
127
|
+
// Guard the contract (`ToolCall.arguments` is an object): a string would be
|
|
128
|
+
// assigned index-wise, inflating the field count from characters.
|
|
129
|
+
if (incoming.arguments && typeof incoming.arguments === 'object') {
|
|
130
|
+
// Mutate rather than re-spread — spreading copied every previously
|
|
131
|
+
// accumulated property on each partial delta (O(n²) as fields grow) — and
|
|
132
|
+
// count only newly-introduced keys so the bound check stays O(delta) too.
|
|
133
|
+
const { addedKeys, addedChars } = assignCountingNewKeys(existing.arguments, incoming.arguments);
|
|
134
|
+
existing.argumentCount += addedKeys;
|
|
135
|
+
assemblyChars += addedChars;
|
|
136
|
+
if (existing.argumentCount > MAX_TOOL_ARGUMENT_KEYS) {
|
|
137
|
+
throw new ToolAssemblyOverflowError(`tool call arguments exceeded ${MAX_TOOL_ARGUMENT_KEYS} fields`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (assemblyChars > MAX_TOOL_ASSEMBLY_CHARS) {
|
|
141
|
+
throw new ToolAssemblyOverflowError(`stream tool-call assembly exceeded ${MAX_TOOL_ASSEMBLY_CHARS} serialized characters`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
64
144
|
}
|
|
145
|
+
if (token.finishReason)
|
|
146
|
+
finishReason = token.finishReason;
|
|
65
147
|
}
|
|
66
|
-
|
|
67
|
-
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
// The backend can throw partway through the stream (Models#wrapStream re-throws
|
|
151
|
+
// mid-stream backend errors). Headers/200 are already flushed, so this can't be an
|
|
152
|
+
// HTTP error status — emit a final OpenAI-shaped `data: {error}` frame so SDK clients
|
|
153
|
+
// see a parseable error (matching the non-streaming path) instead of an abrupt socket
|
|
154
|
+
// close. OpenAI terminates the stream on error and sends no `[DONE]`, so we do the same.
|
|
155
|
+
const error = opts.formatError
|
|
156
|
+
? opts.formatError(err)
|
|
157
|
+
: { message: 'Internal server error', type: 'server_error', code: null, param: null };
|
|
158
|
+
yield { data: { error } };
|
|
159
|
+
return;
|
|
68
160
|
}
|
|
69
161
|
if (toolAssembly.size > 0) {
|
|
70
162
|
const toolCalls = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openaiStream.js","sourceRoot":"","sources":["../../../resources/models/openaiStream.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;
|
|
1
|
+
{"version":3,"file":"openaiStream.js","sourceRoot":"","sources":["../../../resources/models/openaiStream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /v1/chat/completions` — OpenAI-compatible chat endpoint (#631).
|
|
3
|
+
*
|
|
4
|
+
* SSE serving-path note: the OpenAI SDK sends `Accept: application/json` for
|
|
5
|
+
* ALL requests including streaming ones (`client.ts:1160` in the SDK source).
|
|
6
|
+
* Harper's REST layer dispatches `Accept: text/event-stream` as CONNECT, and
|
|
7
|
+
* everything else as the HTTP method. So `stream: true` from an OpenAI SDK
|
|
8
|
+
* client reaches this `post()` handler, NOT `connect()`. We detect the `stream`
|
|
9
|
+
* flag in the body and return `{ body: Readable }` which REST.ts bypasses
|
|
10
|
+
* serialisation on (REST.ts:165-193) — exactly like any SSE resource response,
|
|
11
|
+
* but initiated from `post()` rather than `connect()`.
|
|
12
|
+
*/
|
|
13
|
+
import type { Readable } from 'node:stream';
|
|
14
|
+
import { Resource } from '../../Resource.ts';
|
|
15
|
+
export declare class V1ChatCompletions extends Resource {
|
|
16
|
+
static reservedPath: boolean;
|
|
17
|
+
static post(_target: unknown, body: unknown, request: unknown): Promise<import("./errors.ts").OpenAIErrorResponse | import("./translation.ts").OAIChatCompletion | {
|
|
18
|
+
status: number;
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': string;
|
|
21
|
+
'Cache-Control': string;
|
|
22
|
+
'X-Accel-Buffering': string;
|
|
23
|
+
};
|
|
24
|
+
body: Readable;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* A client that sends an explicit `Accept: text/event-stream` with its POST is
|
|
28
|
+
* dispatched by REST as CONNECT (REST.ts), not POST. The OpenAI SDK happens to send
|
|
29
|
+
* `Accept: application/json` even when streaming, but other valid SSE clients do not.
|
|
30
|
+
*
|
|
31
|
+
* Without this override the request reached `Resource`'s default `connect`, whose
|
|
32
|
+
* instance path returns `subscribe()` — an empty `IterableEventQueue` — so the client
|
|
33
|
+
* got a 200 SSE response that stayed open forever emitting nothing, rather than an
|
|
34
|
+
* error it could act on.
|
|
35
|
+
*
|
|
36
|
+
* REST passes `null` as the CONNECT body (`resource.connect(target, null, request)`),
|
|
37
|
+
* so the parsed body is taken off the request and handed to the same `post()`
|
|
38
|
+
* implementation — one code path, identical validation and error shaping.
|
|
39
|
+
*
|
|
40
|
+
* `connect` is also reachable from the WebSocket handler with a different signature
|
|
41
|
+
* (`resourceRequest, incomingMessages, request`), where there is no `request.data`;
|
|
42
|
+
* that case is rejected as a client error rather than returning an envelope the WS
|
|
43
|
+
* path would fail to iterate.
|
|
44
|
+
*/
|
|
45
|
+
static connect(target: unknown, _data: unknown, request: unknown): Promise<import("./errors.ts").OpenAIErrorResponse | import("./translation.ts").OAIChatCompletion | {
|
|
46
|
+
status: number;
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': string;
|
|
49
|
+
'Cache-Control': string;
|
|
50
|
+
'X-Accel-Buffering': string;
|
|
51
|
+
};
|
|
52
|
+
body: Readable;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `POST /v1/chat/completions` — OpenAI-compatible chat endpoint (#631).
|
|
4
|
+
*
|
|
5
|
+
* SSE serving-path note: the OpenAI SDK sends `Accept: application/json` for
|
|
6
|
+
* ALL requests including streaming ones (`client.ts:1160` in the SDK source).
|
|
7
|
+
* Harper's REST layer dispatches `Accept: text/event-stream` as CONNECT, and
|
|
8
|
+
* everything else as the HTTP method. So `stream: true` from an OpenAI SDK
|
|
9
|
+
* client reaches this `post()` handler, NOT `connect()`. We detect the `stream`
|
|
10
|
+
* flag in the body and return `{ body: Readable }` which REST.ts bypasses
|
|
11
|
+
* serialisation on (REST.ts:165-193) — exactly like any SSE resource response,
|
|
12
|
+
* but initiated from `post()` rather than `connect()`.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.V1ChatCompletions = void 0;
|
|
16
|
+
const contentTypes_ts_1 = require("../../../server/serverHelpers/contentTypes.js");
|
|
17
|
+
const Resource_ts_1 = require("../../Resource.js");
|
|
18
|
+
const Models_ts_1 = require("../Models.js");
|
|
19
|
+
const openaiStream_ts_1 = require("../openaiStream.js");
|
|
20
|
+
const errors_ts_1 = require("./errors.js");
|
|
21
|
+
const translation_ts_1 = require("./translation.js");
|
|
22
|
+
const sseHandler = contentTypes_ts_1.contentTypes.get('text/event-stream');
|
|
23
|
+
// @ts-ignore — Resource base class is not typed for static dispatch; pattern mirrors login.ts
|
|
24
|
+
class V1ChatCompletions extends Resource_ts_1.Resource {
|
|
25
|
+
// Reserve this fixed route: a later app registration at the same path becomes a
|
|
26
|
+
// loud conflict (ErrorResource) instead of silently replacing the gateway and its
|
|
27
|
+
// super_user gate. See Resources.set.
|
|
28
|
+
static reservedPath = true;
|
|
29
|
+
static async post(_target, body, request) {
|
|
30
|
+
const authError = (0, errors_ts_1.authorizeV1Request)(request);
|
|
31
|
+
if (authError)
|
|
32
|
+
return authError;
|
|
33
|
+
// REST.ts passes `request.data` directly, which is the (unawaited) streaming
|
|
34
|
+
// JSON deserializer's Promise — awaiting here is a no-op for callers (e.g.
|
|
35
|
+
// unit tests) that already pass a plain object. A malformed JSON body rejects
|
|
36
|
+
// this promise, which is a client error, not a 500.
|
|
37
|
+
try {
|
|
38
|
+
body = await body;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return (0, errors_ts_1.badRequest)(`Could not parse request body: ${err instanceof Error ? err.message : 'invalid JSON'}`);
|
|
42
|
+
}
|
|
43
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
44
|
+
return (0, errors_ts_1.badRequest)('Request body must be a JSON object');
|
|
45
|
+
}
|
|
46
|
+
const req = body;
|
|
47
|
+
// Validate the nested wire shapes before mapping: the mappers assume well-formed
|
|
48
|
+
// input, so an unvalidated `messages:[null]` / `tools:[{}]` would throw a TypeError
|
|
49
|
+
// and surface as an RFC 9457 500 instead of an OpenAI 400.
|
|
50
|
+
const invalid = (0, translation_ts_1.validateChatRequest)(req);
|
|
51
|
+
if (invalid)
|
|
52
|
+
return (0, errors_ts_1.badRequest)(invalid);
|
|
53
|
+
const model = typeof req.model === 'string' ? req.model : 'default';
|
|
54
|
+
try {
|
|
55
|
+
const messages = (0, translation_ts_1.translateMessages)(req.messages);
|
|
56
|
+
// tool_choice: 'none' means "do not call tools" — the only faithful way to honor
|
|
57
|
+
// that against a returns-tool-calls backend is to not offer the tools at all.
|
|
58
|
+
// 'required'/named selection are rejected in validateChatRequest.
|
|
59
|
+
const tools = req.tool_choice === 'none' || !req.tools?.length ? undefined : (0, translation_ts_1.translateTools)(req.tools);
|
|
60
|
+
const input = (0, translation_ts_1.toGenerateInput)(messages, tools);
|
|
61
|
+
const opts = (0, translation_ts_1.toGenerateOpts)(req);
|
|
62
|
+
if (req.stream) {
|
|
63
|
+
const tokenStream = Models_ts_1.models.generateStream(input, opts);
|
|
64
|
+
// serializeStream wraps the async iterable in a Node Readable so REST.ts
|
|
65
|
+
// can return it without re-serialising. The `body` presence on the return
|
|
66
|
+
// value skips REST.ts's own serialize() call (REST.ts:165-193).
|
|
67
|
+
// formatError reuses the non-streaming error mapping so a mid-stream backend
|
|
68
|
+
// failure reaches the client as an OpenAI-shaped SSE error frame.
|
|
69
|
+
const readable = sseHandler.serializeStream((0, openaiStream_ts_1.openaiStream)(tokenStream, { model, formatError: (err) => (0, errors_ts_1.toOpenAIError)(err).data.error }));
|
|
70
|
+
return {
|
|
71
|
+
status: 200,
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'text/event-stream',
|
|
74
|
+
'Cache-Control': 'no-cache',
|
|
75
|
+
'X-Accel-Buffering': 'no',
|
|
76
|
+
},
|
|
77
|
+
body: readable,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const result = await Models_ts_1.models.generate(input, opts);
|
|
81
|
+
return (0, translation_ts_1.toChatCompletion)(result, model);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return (0, errors_ts_1.toOpenAIError)(err);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A client that sends an explicit `Accept: text/event-stream` with its POST is
|
|
89
|
+
* dispatched by REST as CONNECT (REST.ts), not POST. The OpenAI SDK happens to send
|
|
90
|
+
* `Accept: application/json` even when streaming, but other valid SSE clients do not.
|
|
91
|
+
*
|
|
92
|
+
* Without this override the request reached `Resource`'s default `connect`, whose
|
|
93
|
+
* instance path returns `subscribe()` — an empty `IterableEventQueue` — so the client
|
|
94
|
+
* got a 200 SSE response that stayed open forever emitting nothing, rather than an
|
|
95
|
+
* error it could act on.
|
|
96
|
+
*
|
|
97
|
+
* REST passes `null` as the CONNECT body (`resource.connect(target, null, request)`),
|
|
98
|
+
* so the parsed body is taken off the request and handed to the same `post()`
|
|
99
|
+
* implementation — one code path, identical validation and error shaping.
|
|
100
|
+
*
|
|
101
|
+
* `connect` is also reachable from the WebSocket handler with a different signature
|
|
102
|
+
* (`resourceRequest, incomingMessages, request`), where there is no `request.data`;
|
|
103
|
+
* that case is rejected as a client error rather than returning an envelope the WS
|
|
104
|
+
* path would fail to iterate.
|
|
105
|
+
*/
|
|
106
|
+
static async connect(target, _data, request) {
|
|
107
|
+
const data = request?.data;
|
|
108
|
+
if (data === undefined) {
|
|
109
|
+
return (0, errors_ts_1.badRequest)('This endpoint requires a JSON request body; WebSocket connections are not supported');
|
|
110
|
+
}
|
|
111
|
+
return this.post(target, data, request);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.V1ChatCompletions = V1ChatCompletions;
|
|
115
|
+
//# sourceMappingURL=chatCompletions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatCompletions.js","sourceRoot":"","sources":["../../../../resources/models/v1/chatCompletions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /v1/embeddings` — OpenAI-compatible embedding endpoint (#631).
|
|
3
|
+
*
|
|
4
|
+
* Maps OpenAI's `{ model, input }` request body to `scope.models.embed()` and
|
|
5
|
+
* returns `{ object: 'list', data: [...], model, usage }` per the OpenAI wire spec.
|
|
6
|
+
*/
|
|
7
|
+
import { Resource } from '../../Resource.ts';
|
|
8
|
+
export declare class V1Embeddings extends Resource {
|
|
9
|
+
static reservedPath: boolean;
|
|
10
|
+
static post(_target: unknown, body: Record<string, unknown>, request: unknown): Promise<import("./errors.ts").OpenAIErrorResponse | import("./translation.ts").OAIEmbedResponse>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `POST /v1/embeddings` — OpenAI-compatible embedding endpoint (#631).
|
|
4
|
+
*
|
|
5
|
+
* Maps OpenAI's `{ model, input }` request body to `scope.models.embed()` and
|
|
6
|
+
* returns `{ object: 'list', data: [...], model, usage }` per the OpenAI wire spec.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.V1Embeddings = void 0;
|
|
10
|
+
const Resource_ts_1 = require("../../Resource.js");
|
|
11
|
+
const Models_ts_1 = require("../Models.js");
|
|
12
|
+
const errors_ts_1 = require("./errors.js");
|
|
13
|
+
const translation_ts_1 = require("./translation.js");
|
|
14
|
+
// Cap batched input, matching OpenAI's own 2048-item limit. The endpoint is
|
|
15
|
+
// super_user-only and off by default, so this is a sanity bound (avoid an
|
|
16
|
+
// unbounded fan-out to the backend), not a security control.
|
|
17
|
+
const MAX_EMBEDDING_INPUTS = 2048;
|
|
18
|
+
// @ts-ignore — Resource base class is not typed for static dispatch; pattern mirrors login.ts
|
|
19
|
+
class V1Embeddings extends Resource_ts_1.Resource {
|
|
20
|
+
// Reserve this fixed route: a later app registration at the same path becomes a
|
|
21
|
+
// loud conflict (ErrorResource) instead of silently replacing the gateway and its
|
|
22
|
+
// super_user gate. See Resources.set.
|
|
23
|
+
static reservedPath = true;
|
|
24
|
+
static async post(_target, body, request) {
|
|
25
|
+
const authError = (0, errors_ts_1.authorizeV1Request)(request);
|
|
26
|
+
if (authError)
|
|
27
|
+
return authError;
|
|
28
|
+
// REST.ts passes `request.data` directly, which is the (unawaited) streaming
|
|
29
|
+
// JSON deserializer's Promise — awaiting here is a no-op for callers (e.g.
|
|
30
|
+
// unit tests) that already pass a plain object. A malformed JSON body rejects
|
|
31
|
+
// this promise, which is a client error, not a 500 (matches chatCompletions).
|
|
32
|
+
try {
|
|
33
|
+
body = await body;
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return (0, errors_ts_1.badRequest)(`Could not parse request body: ${err instanceof Error ? err.message : 'invalid JSON'}`);
|
|
37
|
+
}
|
|
38
|
+
if (!body || typeof body !== 'object' || Array.isArray(body))
|
|
39
|
+
return (0, errors_ts_1.badRequest)('Request body must be a JSON object');
|
|
40
|
+
const raw = body;
|
|
41
|
+
// Mirrors validateChatRequest: a non-string model would silently invoke the
|
|
42
|
+
// configured default rather than being rejected.
|
|
43
|
+
if (raw.model !== undefined && typeof raw.model !== 'string')
|
|
44
|
+
return (0, errors_ts_1.badRequest)("'model' must be a string");
|
|
45
|
+
const input = raw.input;
|
|
46
|
+
if (input === undefined || input === null)
|
|
47
|
+
return (0, errors_ts_1.badRequest)("'input' is required");
|
|
48
|
+
if (typeof input !== 'string' && !Array.isArray(input)) {
|
|
49
|
+
return (0, errors_ts_1.badRequest)("'input' must be a string or array of strings");
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(input) && !input.every((v) => typeof v === 'string')) {
|
|
52
|
+
return (0, errors_ts_1.badRequest)("'input' array elements must be strings");
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(input) && input.length > MAX_EMBEDDING_INPUTS) {
|
|
55
|
+
return (0, errors_ts_1.badRequest)(`'input' array must not exceed ${MAX_EMBEDDING_INPUTS} items`);
|
|
56
|
+
}
|
|
57
|
+
const model = typeof raw.model === 'string' ? raw.model : 'default';
|
|
58
|
+
const opts = (0, translation_ts_1.toEmbedOpts)(raw);
|
|
59
|
+
try {
|
|
60
|
+
// embedWithUsage, not embed(): the public facade drops the result-level usage
|
|
61
|
+
// backends report, and OpenAI clients read real token counts off the response.
|
|
62
|
+
const { vectors, usage } = await Models_ts_1.models.embedWithUsage(input, opts);
|
|
63
|
+
return (0, translation_ts_1.toEmbedResponse)(vectors, model, usage);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return (0, errors_ts_1.toOpenAIError)(err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.V1Embeddings = V1Embeddings;
|
|
71
|
+
//# sourceMappingURL=embeddings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../../../../resources/models/v1/embeddings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI error envelope helpers for the `/v1/*` gateway (#631).
|
|
3
|
+
*
|
|
4
|
+
* Harper's REST layer serialises uncaught errors as RFC 9457 Problem Details.
|
|
5
|
+
* Resources that need the OpenAI `{ error: { message, type, code, param } }`
|
|
6
|
+
* shape must catch errors themselves and call `toOpenAIError()` / `badRequest()`.
|
|
7
|
+
*/
|
|
8
|
+
type OpenAIErrorType = 'invalid_request_error' | 'server_error' | 'authentication_error' | 'permission_error' | 'api_error';
|
|
9
|
+
export interface OpenAIErrorBody {
|
|
10
|
+
message: string;
|
|
11
|
+
type: OpenAIErrorType;
|
|
12
|
+
code: string | null;
|
|
13
|
+
param: string | null;
|
|
14
|
+
}
|
|
15
|
+
/** HTTP response payload from a gateway error; resource methods return this directly. */
|
|
16
|
+
export interface OpenAIErrorResponse {
|
|
17
|
+
status: number;
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/json';
|
|
20
|
+
};
|
|
21
|
+
data: {
|
|
22
|
+
error: OpenAIErrorBody;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Map any thrown value to an OpenAI error envelope. Uses `statusCode` when
|
|
27
|
+
* present (Harper's `ClientError` / `ServerError` convention). Falls back to
|
|
28
|
+
* `500 server_error`. `ModelBackendNotFoundError` maps to `404 model_not_found`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function toOpenAIError(err: unknown): OpenAIErrorResponse;
|
|
31
|
+
/** Convenience for early request-body validation failures. */
|
|
32
|
+
export declare function badRequest(message: string): OpenAIErrorResponse;
|
|
33
|
+
/**
|
|
34
|
+
* Gate for the `/v1/*` handlers, since overriding the static `get`/`post` methods
|
|
35
|
+
* bypasses Resource's `transactional()` wrapper and its default `allowRead`/`allowCreate`
|
|
36
|
+
* checks (Resource.ts:685-733, 426-435) never run for these endpoints.
|
|
37
|
+
*
|
|
38
|
+
* Mirrors Resource's default gate (super_user-only) rather than introducing a new
|
|
39
|
+
* permission — see PR discussion for whether a dedicated `/v1/*` permission should
|
|
40
|
+
* replace this later.
|
|
41
|
+
*
|
|
42
|
+
* Returns an OpenAI-shape error response when access should be denied, or `null`
|
|
43
|
+
* when the request may proceed.
|
|
44
|
+
*/
|
|
45
|
+
export declare function authorizeV1Request(request: {
|
|
46
|
+
user?: {
|
|
47
|
+
role?: {
|
|
48
|
+
permission?: {
|
|
49
|
+
super_user?: boolean;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
}): OpenAIErrorResponse | null;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI error envelope helpers for the `/v1/*` gateway (#631).
|
|
4
|
+
*
|
|
5
|
+
* Harper's REST layer serialises uncaught errors as RFC 9457 Problem Details.
|
|
6
|
+
* Resources that need the OpenAI `{ error: { message, type, code, param } }`
|
|
7
|
+
* shape must catch errors themselves and call `toOpenAIError()` / `badRequest()`.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.toOpenAIError = toOpenAIError;
|
|
14
|
+
exports.badRequest = badRequest;
|
|
15
|
+
exports.authorizeV1Request = authorizeV1Request;
|
|
16
|
+
const backendRegistry_ts_1 = require("../backendRegistry.js");
|
|
17
|
+
const Models_ts_1 = require("../Models.js");
|
|
18
|
+
const harper_logger_ts_1 = __importDefault(require("../../../utility/logging/harper_logger.js"));
|
|
19
|
+
/**
|
|
20
|
+
* Map any thrown value to an OpenAI error envelope. Uses `statusCode` when
|
|
21
|
+
* present (Harper's `ClientError` / `ServerError` convention). Falls back to
|
|
22
|
+
* `500 server_error`. `ModelBackendNotFoundError` maps to `404 model_not_found`.
|
|
23
|
+
*/
|
|
24
|
+
function toOpenAIError(err) {
|
|
25
|
+
let status = 500;
|
|
26
|
+
let type = 'server_error';
|
|
27
|
+
let code = null;
|
|
28
|
+
if (err instanceof backendRegistry_ts_1.ModelBackendNotFoundError) {
|
|
29
|
+
status = 404;
|
|
30
|
+
type = 'invalid_request_error';
|
|
31
|
+
code = 'model_not_found';
|
|
32
|
+
}
|
|
33
|
+
else if (err instanceof Models_ts_1.ModelCapabilityError) {
|
|
34
|
+
// Caller-driven mismatch (e.g. `tools` or streaming against a backend that
|
|
35
|
+
// doesn't support it): the request is what's wrong, not the server. It extends
|
|
36
|
+
// ServerError (statusCode 500), so this must precede the statusCode branch —
|
|
37
|
+
// falling through would report a generic sanitized 500 for a client-actionable
|
|
38
|
+
// condition. The message is safe to pass through: it names only the backend and
|
|
39
|
+
// the capability the caller asked for.
|
|
40
|
+
status = 400;
|
|
41
|
+
type = 'invalid_request_error';
|
|
42
|
+
code = 'capability_unsupported';
|
|
43
|
+
}
|
|
44
|
+
else if (err instanceof Error && typeof err.statusCode === 'number') {
|
|
45
|
+
status = err.statusCode;
|
|
46
|
+
if (status === 401) {
|
|
47
|
+
type = 'authentication_error';
|
|
48
|
+
}
|
|
49
|
+
else if (status === 403) {
|
|
50
|
+
// OpenAI semantics: 401 = bad/missing credentials, 403 = valid credentials
|
|
51
|
+
// lacking permission (matches authorizeV1Request's own 403 envelope).
|
|
52
|
+
type = 'permission_error';
|
|
53
|
+
}
|
|
54
|
+
else if (status < 500) {
|
|
55
|
+
type = 'invalid_request_error';
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
type = 'server_error';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// 5xx messages stay generic: internal error strings (backend stack details, file
|
|
62
|
+
// paths) don't belong in a wire response. The real error goes to the log. 4xx
|
|
63
|
+
// messages are client-actionable and pass through.
|
|
64
|
+
let message;
|
|
65
|
+
if (status >= 500) {
|
|
66
|
+
harper_logger_ts_1.default.error('v1 gateway error', err);
|
|
67
|
+
message = 'Internal server error';
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
message = err instanceof Error ? err.message : 'Bad request';
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
status,
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
data: { error: { message, type, code, param: null } },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Convenience for early request-body validation failures. */
|
|
79
|
+
function badRequest(message) {
|
|
80
|
+
return {
|
|
81
|
+
status: 400,
|
|
82
|
+
headers: { 'Content-Type': 'application/json' },
|
|
83
|
+
data: { error: { message, type: 'invalid_request_error', code: null, param: null } },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Gate for the `/v1/*` handlers, since overriding the static `get`/`post` methods
|
|
88
|
+
* bypasses Resource's `transactional()` wrapper and its default `allowRead`/`allowCreate`
|
|
89
|
+
* checks (Resource.ts:685-733, 426-435) never run for these endpoints.
|
|
90
|
+
*
|
|
91
|
+
* Mirrors Resource's default gate (super_user-only) rather than introducing a new
|
|
92
|
+
* permission — see PR discussion for whether a dedicated `/v1/*` permission should
|
|
93
|
+
* replace this later.
|
|
94
|
+
*
|
|
95
|
+
* Returns an OpenAI-shape error response when access should be denied, or `null`
|
|
96
|
+
* when the request may proceed.
|
|
97
|
+
*/
|
|
98
|
+
function authorizeV1Request(request) {
|
|
99
|
+
const user = request?.user;
|
|
100
|
+
if (!user) {
|
|
101
|
+
return {
|
|
102
|
+
status: 401,
|
|
103
|
+
headers: { 'Content-Type': 'application/json' },
|
|
104
|
+
data: {
|
|
105
|
+
error: {
|
|
106
|
+
message: 'You must provide valid credentials to access this endpoint.',
|
|
107
|
+
type: 'authentication_error',
|
|
108
|
+
code: null,
|
|
109
|
+
param: null,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (!user.role?.permission?.super_user) {
|
|
115
|
+
return {
|
|
116
|
+
status: 403,
|
|
117
|
+
headers: { 'Content-Type': 'application/json' },
|
|
118
|
+
data: {
|
|
119
|
+
error: {
|
|
120
|
+
message: 'You do not have permission to access this endpoint.',
|
|
121
|
+
type: 'permission_error',
|
|
122
|
+
code: null,
|
|
123
|
+
param: null,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../../resources/models/v1/errors.ts"],"names":[],"mappings":"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"}
|