dsh-modellix 0.2.0 → 0.3.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/CHANGELOG.md +32 -0
- package/README.md +17 -5
- package/README.zh-CN.md +17 -5
- package/cordis.patch.yml +0 -13
- package/docs/en-US/LOCAL_USAGE.md +14 -10
- package/docs/en-US/RELEASE_CHECKLIST.md +7 -7
- package/docs/en-US/USER_GUIDE.md +21 -8
- package/docs/zh-CN/LOCAL_USAGE.md +14 -10
- package/docs/zh-CN/RELEASE_CHECKLIST.md +7 -7
- package/docs/zh-CN/USER_GUIDE.md +21 -8
- package/lib/client.d.ts +2 -2
- package/lib/client.js +26 -7
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +16 -6
- package/lib/index.js +212 -59
- package/lib/index.js.map +1 -1
- package/package.json +71 -52
package/lib/index.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { z as z$1 } from "zod";
|
|
|
7
7
|
import { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
8
8
|
import { Context } from "@deepseek-ai/cordis";
|
|
9
9
|
import { Agent } from "@deepseek-ai/dsh-agent";
|
|
10
|
-
import { ImageMediaType } from "@deepseek-ai/dsh-attachment";
|
|
11
10
|
|
|
12
11
|
//#region src/core/config.d.ts
|
|
13
12
|
declare const CURRENT_CONFIG_SCHEMA_VERSION: 1;
|
|
@@ -530,7 +529,7 @@ declare function extractAllowedSubmitUrl(document: Readonly<Record<string, unkno
|
|
|
530
529
|
interface PreparedMediaUpload {
|
|
531
530
|
readonly bytes: Uint8Array;
|
|
532
531
|
readonly filename: string;
|
|
533
|
-
readonly mimeType:
|
|
532
|
+
readonly mimeType: string;
|
|
534
533
|
}
|
|
535
534
|
interface MediaUploadRecord {
|
|
536
535
|
readonly fileId: string;
|
|
@@ -555,11 +554,14 @@ declare class MediaUploadClient {
|
|
|
555
554
|
declare function prepareMediaUploadFromSession(agent: Agent | undefined, attachmentId: string, signal?: AbortSignal): Promise<PreparedMediaUpload>;
|
|
556
555
|
declare function prepareMediaUploadFromPath(cwd: string | undefined, inputPath: string): Promise<PreparedMediaUpload>;
|
|
557
556
|
//#endregion
|
|
558
|
-
//#region src/
|
|
557
|
+
//#region src/shared/json-value.d.ts
|
|
558
|
+
/** Lossless JSON values shared across Harness versions. */
|
|
559
559
|
type JsonPrimitive = null | boolean | number | string;
|
|
560
|
-
type JsonValue = JsonPrimitive |
|
|
561
|
-
|
|
560
|
+
type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
561
|
+
[key: string]: JsonValue;
|
|
562
562
|
};
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/design/schema-ir.d.ts
|
|
563
565
|
type UiFieldKind = "string" | "number" | "integer" | "boolean" | "object" | "array" | "media" | "unknown";
|
|
564
566
|
type UiMediaKind = "image" | "video" | "audio";
|
|
565
567
|
interface UiConstraints {
|
|
@@ -718,6 +720,7 @@ interface ReadPredictionInput {
|
|
|
718
720
|
readonly apiKey: string;
|
|
719
721
|
readonly maxAttempts?: number;
|
|
720
722
|
readonly signal?: AbortSignal;
|
|
723
|
+
readonly waitSeconds?: number;
|
|
721
724
|
}
|
|
722
725
|
declare class PredictionClient {
|
|
723
726
|
#private;
|
|
@@ -726,6 +729,10 @@ declare class PredictionClient {
|
|
|
726
729
|
submit(input: SubmitPredictionInput): Promise<PredictionTask>;
|
|
727
730
|
/** GET retries only transient read failures and is capped at five attempts. */
|
|
728
731
|
readTask(input: ReadPredictionInput): Promise<PredictionTask>;
|
|
732
|
+
/** Wait for one bounded server observation; does not submit or sleep between snapshots. */
|
|
733
|
+
waitTask(input: Omit<ReadPredictionInput, "waitSeconds"> & {
|
|
734
|
+
readonly timeoutSeconds?: number;
|
|
735
|
+
}): Promise<PredictionTask>;
|
|
729
736
|
}
|
|
730
737
|
declare function validateSubmitEndpoint(endpoint: string, modelSlug: string): URL;
|
|
731
738
|
declare function parsePredictionTask(payload: unknown, expectedTaskId?: string): PredictionTask | null;
|
|
@@ -894,6 +901,7 @@ interface CredentialSnapshot {
|
|
|
894
901
|
readonly credentialEpoch: number;
|
|
895
902
|
}
|
|
896
903
|
interface DesignHostControllerOptions {
|
|
904
|
+
readonly serializeObservation?: (operation: () => Promise<void>) => Promise<void>;
|
|
897
905
|
readonly storage: StoragePort;
|
|
898
906
|
readonly resolveCredential: () => Promise<CredentialSnapshot | undefined>;
|
|
899
907
|
readonly isCredentialEpochCurrent: (credentialEpoch: number) => boolean;
|
|
@@ -985,6 +993,7 @@ declare class DesignHostController {
|
|
|
985
993
|
constructor(options: DesignHostControllerOptions);
|
|
986
994
|
handle(endpoint: string, payload: unknown, signal?: AbortSignal): Promise<DesignSnapshotWire>;
|
|
987
995
|
pollRunning(signal?: AbortSignal): Promise<boolean>;
|
|
996
|
+
invalidateWaits(): void;
|
|
988
997
|
private read;
|
|
989
998
|
private refresh;
|
|
990
999
|
private selectModel;
|
|
@@ -1100,6 +1109,7 @@ declare class ModellixRuntime {
|
|
|
1100
1109
|
private enqueueDesignWrite;
|
|
1101
1110
|
private rememberDesignModel;
|
|
1102
1111
|
private syncDesignTools;
|
|
1112
|
+
private syncWebTools;
|
|
1103
1113
|
private scheduleDesignPoll;
|
|
1104
1114
|
}
|
|
1105
1115
|
//#endregion
|
|
@@ -1445,5 +1455,5 @@ interface Config {}
|
|
|
1445
1455
|
declare const Config: z<Config>;
|
|
1446
1456
|
declare function apply(ctx: Context): Promise<void>;
|
|
1447
1457
|
//#endregion
|
|
1448
|
-
export { AUTHENTICATED_CATALOG_URL, ApprovedHttpRequest, AvailableDesignResult, BeginLlmMaterializationInput, BeginOnboardingSaveInput, CURRENT_CONFIG_SCHEMA_VERSION, CacheEntry, CachePort, ClockPort, Config, CredentialBroker, CredentialBrokerOptions, CredentialDescriptor, CredentialEpochConflictError, CredentialMutationCoordinator, CredentialMutationResult, CredentialSource, CredentialState, CredentialValidationError, CredentialVerification, DEFAULT_DESIGN_WAL_KEY, DEFAULT_RESULT_TTL_MS, DEFAULT_WEB_RESPONSE_BYTES, DEFAULT_WEB_SEARCH_MAX_RESULTS, DESIGN_PLANNER_ENDPOINT, DESIGN_PLANNER_MODEL, DesignConfig, DesignError, DesignErrorCode, DesignHostController, DesignHostControllerOptions, DesignLogEvent, DesignLogLevel, DesignMediaCategory, DesignModelSummary, DesignPlannerClient, DesignPlannerClientOptions, DesignPlannerRequest, DesignPlannerResult, DesignPollDiagnosticCode, DesignSchemaIR, DesignSnapshotWire, DesignTaskRecord, DesignTaskRepository, DesignTaskRepositoryOptions, DesignTaskState, DesignToolController, DesignWalEvent, EMPTY_LLM_ROUTE_LEDGER, ExactParameterPatch, FetchPort, HarnessCredentialInfo, HarnessCredentialPort, HeaderRecord, HeaderValue, HttpMethod, HttpPolicyError, HttpRequestPolicyInput, HttpResponseBoundaryError, HttpRetryFailure, InvalidCredentialEpoch, JsonPrimitive, JsonValue, LlmCatalogCache, LlmCatalogCacheOptions, LlmCatalogClient, LlmCatalogClientOptions, LlmCatalogRequestError, LlmConfig, LlmCredentialSnapshot, LlmInterruptedMaterializationEvidence, LlmInterruptedMaterializationResult, LlmMaterializationConflictError, LlmMaterializationReceipt, LlmMaterializationRecovery, LlmMaterializationRollbackError, LlmOwnedEntry, LlmOwnershipConfig, LlmPreparedMaterialization, LlmRegistryBackreadError, LlmRegistryReader, LlmRegistryVerificationOptions, LlmRouteConflictError, LlmRouteLedger, LlmRouteOwnership, LlmRouteOwnershipConfig, LlmRouteOwnershipEntry, LlmSettingsMaterializer, LlmSettingsPort, LoggerPort, MAX_WEB_QUERY_CHARS, MAX_WEB_SEARCH_RESULTS, MAX_WEB_URL_CHARS, MODELLIX_CREDENTIAL_REF, MODELLIX_LLM_BASE_URL, MODELLIX_LLM_MODELS_URL, MODELLIX_LLM_PROVENANCE_FIELD, MODELLIX_LLM_PROVIDER_ID, MODELLIX_MEDIA_GENERATE_TOOL, MODELLIX_MEDIA_GET_RESULT_TOOL, MODELLIX_MEDIA_LIST_TOOL, MODELLIX_MEDIA_PREPARE_TOOL, MODELLIX_MEDIA_SCHEMA_TOOL, MODELLIX_MEDIA_UPLOAD_FILE_TOOL, MODELLIX_ORIGINS, MODELLIX_SETTINGS_NAMESPACE, MODELLIX_WEB_FETCH_ENDPOINT, MODELLIX_WEB_FETCH_TOOL, MODELLIX_WEB_PROVIDER_ID, MODELLIX_WEB_SEARCH_ENDPOINT, MODELLIX_WEB_SEARCH_TOOL, MediaUploadClient, MediaUploadInput, MediaUploadRecord, ModelCatalogClient, ModelCatalogClientOptions, ModelCatalogPage, ModelCatalogQuery, ModelSchemaClient, ModelSchemaClientOptions, ModelSchemaDocument, ModellixDesignDomain, ModellixErrorCode, ModellixErrorContext, ModellixErrorContract, ModellixFailure, ModellixLlmCatalog, ModellixLlmModel, ModellixOriginName, ModellixPiAiRoute, ModellixRuntime, ModellixRuntimeState, ModellixService, ModellixWebContractError, ModellixWebCredentialSnapshot, ModellixWebFetchFailedError, ModellixWebFetchProvider, ModellixWebProviderError, ModellixWebProviderOptions, ModellixWebProviders, ModellixWebRegistry, ModellixWebSearchProvider, NaturalLanguagePlan, OnboardingConfig, OnboardingRecoveryAction, OnboardingRecoveryDecision, OnboardingSaveConflictError, OnboardingSavePhase, OnboardingSaveRecovery, OnboardingStatus, PUBLIC_PORTAL_CATALOG_URL, ParsedFetchFailure, ParsedFetchResponse, ParsedFetchSuccess, ParsedSearchResponse, PersistedLlmRouteOwnership, PiAiModelEntry, PluginConfig, PluginSettingsController, PluginSettingsSchema, PluginSettingsSnapshot, PredictionClient, PredictionClientOptions, PredictionResource, PredictionTask, PredictionTaskStatus, PreparedMediaUpload, REDACTED, ReadPredictionInput, RedactedValue, RequestDeadline, RetentionPolicy, RetryDelayInput, RetryOptions, RetrySuccess, RouteMaterializationPlan, RouteRemovalPlan, SchemaDiagnostic, SchemaParserLimits, ServiceId, ServiceToggles, ServicesConfig, SettingsNamespaceDescriptor, SettingsScopeLike, SettingsServiceLike, SleepPort, StaleLlmCatalogError, StoragePort, SubmitPredictionInput, UiConstraints, UiField, UiFieldKind, UiMediaKind, UiVariant, UnauthorizedTransition, UnsupportedConfigVersionError, VerificationTransition, WebConfig, abandonLlmMaterialization, advanceCredentialEpoch, apply, applyCredentialDescriptor, applyExactPatch, applyNaturalLanguage, applyRuntimeUnauthorized, applyVerificationResult, approveHttpRequest, approveRedirect, beginLlmMaterialization, beginOnboardingSave, buildFetchRequest, buildInvocationBody, buildSearchRequest, completeLlmMaterialization, completeOnboardingSave, computeRetryDelay, createCredentialState, createDefaultConfig, createModellixDesignToolDefinitions, createModellixWebProviders, createModellixWebToolDefinitions, deferOnboarding, deriveModellixSessionId, deriveModellixUserId, executeWithRetry, extractAllowedSubmitUrl, getServiceToggles, inject, isAllowedModellixOrigin, isCredentialInvalidError, isPublicHostname, isRetryableError, isRetryableReadFailure, isValidModellixIdentity, markOnboardingCredentialSaved, materializeDefaults, migrateConfig, missingCredentialDescriptor, modellixDesignDomainSpec, name, normalizeCredentialDescriptor, openDesignStorage, parseCatalogPage, parseDesignSchema, parseFetchResponse, parsePredictionTask, parseResources, parseRetryAfter, parseSearchResponse, planLlmRouteMaterialization, planLlmRouteRemoval, prepareMediaUploadFromPath, prepareMediaUploadFromSession, preserveStoredCredentialAfterCandidateFailure, preserveVerificationAfterTransientFailure, readBoundedResponseJson, readBoundedResponseText, reconcileLlmRouteLedgerAfterInterruption, reconcileOnboardingSave, redactForLog, redactHeaders, redactUrl, registerModellixDesignTools, registerModellixWebProviderInstances, registerModellixWebProviders, registerModellixWebTools, replayDesignWal, requestDeadline, retryAfterFromFailure, selectAvailableResults, setServiceToggles, systemClock, systemSleep, toModellixError, validatePublicHttpUrl, validateSubmitEndpoint, verifyLlmRegistryBackread };
|
|
1458
|
+
export { AUTHENTICATED_CATALOG_URL, ApprovedHttpRequest, AvailableDesignResult, BeginLlmMaterializationInput, BeginOnboardingSaveInput, CURRENT_CONFIG_SCHEMA_VERSION, CacheEntry, CachePort, ClockPort, Config, CredentialBroker, CredentialBrokerOptions, CredentialDescriptor, CredentialEpochConflictError, CredentialMutationCoordinator, CredentialMutationResult, CredentialSource, CredentialState, CredentialValidationError, CredentialVerification, DEFAULT_DESIGN_WAL_KEY, DEFAULT_RESULT_TTL_MS, DEFAULT_WEB_RESPONSE_BYTES, DEFAULT_WEB_SEARCH_MAX_RESULTS, DESIGN_PLANNER_ENDPOINT, DESIGN_PLANNER_MODEL, DesignConfig, DesignError, DesignErrorCode, DesignHostController, DesignHostControllerOptions, DesignLogEvent, DesignLogLevel, DesignMediaCategory, DesignModelSummary, DesignPlannerClient, DesignPlannerClientOptions, DesignPlannerRequest, DesignPlannerResult, DesignPollDiagnosticCode, DesignSchemaIR, DesignSnapshotWire, DesignTaskRecord, DesignTaskRepository, DesignTaskRepositoryOptions, DesignTaskState, DesignToolController, DesignWalEvent, EMPTY_LLM_ROUTE_LEDGER, ExactParameterPatch, FetchPort, HarnessCredentialInfo, HarnessCredentialPort, HeaderRecord, HeaderValue, HttpMethod, HttpPolicyError, HttpRequestPolicyInput, HttpResponseBoundaryError, HttpRetryFailure, InvalidCredentialEpoch, type JsonPrimitive, type JsonValue, LlmCatalogCache, LlmCatalogCacheOptions, LlmCatalogClient, LlmCatalogClientOptions, LlmCatalogRequestError, LlmConfig, LlmCredentialSnapshot, LlmInterruptedMaterializationEvidence, LlmInterruptedMaterializationResult, LlmMaterializationConflictError, LlmMaterializationReceipt, LlmMaterializationRecovery, LlmMaterializationRollbackError, LlmOwnedEntry, LlmOwnershipConfig, LlmPreparedMaterialization, LlmRegistryBackreadError, LlmRegistryReader, LlmRegistryVerificationOptions, LlmRouteConflictError, LlmRouteLedger, LlmRouteOwnership, LlmRouteOwnershipConfig, LlmRouteOwnershipEntry, LlmSettingsMaterializer, LlmSettingsPort, LoggerPort, MAX_WEB_QUERY_CHARS, MAX_WEB_SEARCH_RESULTS, MAX_WEB_URL_CHARS, MODELLIX_CREDENTIAL_REF, MODELLIX_LLM_BASE_URL, MODELLIX_LLM_MODELS_URL, MODELLIX_LLM_PROVENANCE_FIELD, MODELLIX_LLM_PROVIDER_ID, MODELLIX_MEDIA_GENERATE_TOOL, MODELLIX_MEDIA_GET_RESULT_TOOL, MODELLIX_MEDIA_LIST_TOOL, MODELLIX_MEDIA_PREPARE_TOOL, MODELLIX_MEDIA_SCHEMA_TOOL, MODELLIX_MEDIA_UPLOAD_FILE_TOOL, MODELLIX_ORIGINS, MODELLIX_SETTINGS_NAMESPACE, MODELLIX_WEB_FETCH_ENDPOINT, MODELLIX_WEB_FETCH_TOOL, MODELLIX_WEB_PROVIDER_ID, MODELLIX_WEB_SEARCH_ENDPOINT, MODELLIX_WEB_SEARCH_TOOL, MediaUploadClient, MediaUploadInput, MediaUploadRecord, ModelCatalogClient, ModelCatalogClientOptions, ModelCatalogPage, ModelCatalogQuery, ModelSchemaClient, ModelSchemaClientOptions, ModelSchemaDocument, ModellixDesignDomain, ModellixErrorCode, ModellixErrorContext, ModellixErrorContract, ModellixFailure, ModellixLlmCatalog, ModellixLlmModel, ModellixOriginName, ModellixPiAiRoute, ModellixRuntime, ModellixRuntimeState, ModellixService, ModellixWebContractError, ModellixWebCredentialSnapshot, ModellixWebFetchFailedError, ModellixWebFetchProvider, ModellixWebProviderError, ModellixWebProviderOptions, ModellixWebProviders, ModellixWebRegistry, ModellixWebSearchProvider, NaturalLanguagePlan, OnboardingConfig, OnboardingRecoveryAction, OnboardingRecoveryDecision, OnboardingSaveConflictError, OnboardingSavePhase, OnboardingSaveRecovery, OnboardingStatus, PUBLIC_PORTAL_CATALOG_URL, ParsedFetchFailure, ParsedFetchResponse, ParsedFetchSuccess, ParsedSearchResponse, PersistedLlmRouteOwnership, PiAiModelEntry, PluginConfig, PluginSettingsController, PluginSettingsSchema, PluginSettingsSnapshot, PredictionClient, PredictionClientOptions, PredictionResource, PredictionTask, PredictionTaskStatus, PreparedMediaUpload, REDACTED, ReadPredictionInput, RedactedValue, RequestDeadline, RetentionPolicy, RetryDelayInput, RetryOptions, RetrySuccess, RouteMaterializationPlan, RouteRemovalPlan, SchemaDiagnostic, SchemaParserLimits, ServiceId, ServiceToggles, ServicesConfig, SettingsNamespaceDescriptor, SettingsScopeLike, SettingsServiceLike, SleepPort, StaleLlmCatalogError, StoragePort, SubmitPredictionInput, UiConstraints, UiField, UiFieldKind, UiMediaKind, UiVariant, UnauthorizedTransition, UnsupportedConfigVersionError, VerificationTransition, WebConfig, abandonLlmMaterialization, advanceCredentialEpoch, apply, applyCredentialDescriptor, applyExactPatch, applyNaturalLanguage, applyRuntimeUnauthorized, applyVerificationResult, approveHttpRequest, approveRedirect, beginLlmMaterialization, beginOnboardingSave, buildFetchRequest, buildInvocationBody, buildSearchRequest, completeLlmMaterialization, completeOnboardingSave, computeRetryDelay, createCredentialState, createDefaultConfig, createModellixDesignToolDefinitions, createModellixWebProviders, createModellixWebToolDefinitions, deferOnboarding, deriveModellixSessionId, deriveModellixUserId, executeWithRetry, extractAllowedSubmitUrl, getServiceToggles, inject, isAllowedModellixOrigin, isCredentialInvalidError, isPublicHostname, isRetryableError, isRetryableReadFailure, isValidModellixIdentity, markOnboardingCredentialSaved, materializeDefaults, migrateConfig, missingCredentialDescriptor, modellixDesignDomainSpec, name, normalizeCredentialDescriptor, openDesignStorage, parseCatalogPage, parseDesignSchema, parseFetchResponse, parsePredictionTask, parseResources, parseRetryAfter, parseSearchResponse, planLlmRouteMaterialization, planLlmRouteRemoval, prepareMediaUploadFromPath, prepareMediaUploadFromSession, preserveStoredCredentialAfterCandidateFailure, preserveVerificationAfterTransientFailure, readBoundedResponseJson, readBoundedResponseText, reconcileLlmRouteLedgerAfterInterruption, reconcileOnboardingSave, redactForLog, redactHeaders, redactUrl, registerModellixDesignTools, registerModellixWebProviderInstances, registerModellixWebProviders, registerModellixWebTools, replayDesignWal, requestDeadline, retryAfterFromFailure, selectAvailableResults, setServiceToggles, systemClock, systemSleep, toModellixError, validatePublicHttpUrl, validateSubmitEndpoint, verifyLlmRegistryBackread };
|
|
1449
1459
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.js
CHANGED
|
@@ -2,9 +2,9 @@ import z from "@deepseek-ai/schemastery";
|
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { getOrCreateAnonymousUserId } from "@deepseek-ai/dsh-anonymous-user-id";
|
|
4
4
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
5
|
-
import { SettingsConflictError
|
|
5
|
+
import { SettingsConflictError } from "@deepseek-ai/dsh-settings";
|
|
6
6
|
import { lstat, open, realpath } from "node:fs/promises";
|
|
7
|
-
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
8
8
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
9
9
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
10
10
|
import { defineDomain } from "@deepseek-ai/dsh-storage-domain";
|
|
@@ -1547,6 +1547,10 @@ var MediaUploadClient = class {
|
|
|
1547
1547
|
throw new DesignError("SUBMIT_UNKNOWN", "The media upload outcome is unknown; do not retry automatically", { status: response.status });
|
|
1548
1548
|
}
|
|
1549
1549
|
if (!response.ok) {
|
|
1550
|
+
if (response.status === 429) {
|
|
1551
|
+
response.body?.cancel().catch(() => void 0);
|
|
1552
|
+
throw new DesignError("SUBMIT_REJECTED", "Upload quota exceeded. List existing files and explicitly delete only unneeded files before trying again; no automatic cleanup was performed", { status: 429 });
|
|
1553
|
+
}
|
|
1550
1554
|
const ambiguous = response.status === 408 || response.status === 409 || response.status === 425 || response.status === 429 || response.status >= 500;
|
|
1551
1555
|
response.body?.cancel().catch(() => void 0);
|
|
1552
1556
|
throw new DesignError(ambiguous ? "SUBMIT_UNKNOWN" : "SUBMIT_REJECTED", ambiguous ? "The media upload outcome is unknown; do not retry automatically" : `The media upload was rejected with HTTP ${String(response.status)}`, { status: response.status });
|
|
@@ -1591,12 +1595,19 @@ async function prepareMediaUploadFromPath(cwd, inputPath) {
|
|
|
1591
1595
|
try {
|
|
1592
1596
|
const opened = await handle.stat();
|
|
1593
1597
|
if (!opened.isFile() || opened.size !== before.size || opened.dev !== before.dev || opened.ino !== before.ino) throw new DesignError("INVALID_ARGUMENT", "Media file changed while it was being opened");
|
|
1594
|
-
|
|
1598
|
+
const bounded = Buffer.alloc(before.size + 1);
|
|
1599
|
+
let offset = 0;
|
|
1600
|
+
while (offset < bounded.length) {
|
|
1601
|
+
const { bytesRead } = await handle.read(bounded, offset, bounded.length - offset, null);
|
|
1602
|
+
if (bytesRead === 0) break;
|
|
1603
|
+
offset += bytesRead;
|
|
1604
|
+
}
|
|
1605
|
+
bytes = bounded.subarray(0, offset);
|
|
1595
1606
|
} finally {
|
|
1596
1607
|
await handle.close();
|
|
1597
1608
|
}
|
|
1598
1609
|
if (bytes.byteLength !== before.size) throw new DesignError("INVALID_ARGUMENT", "Media file changed while it was being read");
|
|
1599
|
-
const mimeType =
|
|
1610
|
+
const mimeType = detectMediaMimeType(bytes, resolved);
|
|
1600
1611
|
return {
|
|
1601
1612
|
bytes,
|
|
1602
1613
|
filename: safeFilename(basename(resolved), mimeType),
|
|
@@ -1611,7 +1622,74 @@ function within(root, candidate) {
|
|
|
1611
1622
|
const suffix = relative(root, candidate);
|
|
1612
1623
|
return suffix === "" || !suffix.startsWith("..") && !isAbsolute(suffix);
|
|
1613
1624
|
}
|
|
1614
|
-
|
|
1625
|
+
const MEDIA_EXTENSIONS = {
|
|
1626
|
+
jpg: "image/jpeg",
|
|
1627
|
+
jpeg: "image/jpeg",
|
|
1628
|
+
png: "image/png",
|
|
1629
|
+
webp: "image/webp",
|
|
1630
|
+
gif: "image/gif",
|
|
1631
|
+
bmp: "image/bmp",
|
|
1632
|
+
tif: "image/tiff",
|
|
1633
|
+
tiff: "image/tiff",
|
|
1634
|
+
heic: "image/heic",
|
|
1635
|
+
heif: "image/heif",
|
|
1636
|
+
mp4: "video/mp4",
|
|
1637
|
+
m4v: "video/x-m4v",
|
|
1638
|
+
webm: "video/webm",
|
|
1639
|
+
mov: "video/quicktime",
|
|
1640
|
+
mkv: "video/x-matroska",
|
|
1641
|
+
avi: "video/x-msvideo",
|
|
1642
|
+
mp3: "audio/mpeg",
|
|
1643
|
+
wav: "audio/wav",
|
|
1644
|
+
m4a: "audio/mp4",
|
|
1645
|
+
aac: "audio/aac",
|
|
1646
|
+
ogg: "audio/ogg",
|
|
1647
|
+
flac: "audio/flac",
|
|
1648
|
+
opus: "audio/opus",
|
|
1649
|
+
weba: "audio/webm"
|
|
1650
|
+
};
|
|
1651
|
+
function detectMediaMimeType(bytes, filename) {
|
|
1652
|
+
const extension = extname(filename).slice(1).toLowerCase();
|
|
1653
|
+
const expected = MEDIA_EXTENSIONS[extension];
|
|
1654
|
+
if (expected === void 0) throw new DesignError("INVALID_ARGUMENT", "Unsupported media file extension");
|
|
1655
|
+
const head = Buffer.from(bytes.subarray(0, 16)).toString("latin1");
|
|
1656
|
+
const riffType = head.startsWith("RIFF") ? head.slice(8, 12) : "";
|
|
1657
|
+
const iso = bytes.length >= 12 && head.slice(4, 8) === "ftyp";
|
|
1658
|
+
const ebml = head.startsWith("Eߣ");
|
|
1659
|
+
const audioFrame = bytes.length >= 2 && bytes[0] === 255 && ((bytes[1] ?? 0) & 224) === 224;
|
|
1660
|
+
const tiff = [
|
|
1661
|
+
73,
|
|
1662
|
+
73,
|
|
1663
|
+
42,
|
|
1664
|
+
0
|
|
1665
|
+
].every((byte, index) => bytes[index] === byte) || [
|
|
1666
|
+
77,
|
|
1667
|
+
77,
|
|
1668
|
+
0,
|
|
1669
|
+
42
|
|
1670
|
+
].every((byte, index) => bytes[index] === byte);
|
|
1671
|
+
if ({
|
|
1672
|
+
gif: /^GIF8[79]a/u.test(head),
|
|
1673
|
+
bmp: head.startsWith("BM"),
|
|
1674
|
+
tif: tiff,
|
|
1675
|
+
tiff,
|
|
1676
|
+
heic: iso && /^(?:heic|heix|hevc|hevx|mif1)$/u.test(head.slice(8, 12)),
|
|
1677
|
+
heif: iso && /^(?:heic|heix|hevc|hevx|mif1|msf1)$/u.test(head.slice(8, 12)),
|
|
1678
|
+
mp4: iso,
|
|
1679
|
+
m4v: iso,
|
|
1680
|
+
m4a: iso,
|
|
1681
|
+
mov: iso || head.slice(4, 8) === "moov",
|
|
1682
|
+
avi: riffType === "AVI ",
|
|
1683
|
+
wav: riffType === "WAVE",
|
|
1684
|
+
webm: ebml,
|
|
1685
|
+
mkv: ebml,
|
|
1686
|
+
weba: ebml,
|
|
1687
|
+
mp3: head.startsWith("ID3") || audioFrame,
|
|
1688
|
+
aac: audioFrame,
|
|
1689
|
+
ogg: head.startsWith("OggS"),
|
|
1690
|
+
opus: head.startsWith("OggS"),
|
|
1691
|
+
flac: head.startsWith("fLaC")
|
|
1692
|
+
}[extension] === true) return expected;
|
|
1615
1693
|
if (bytes.length >= 8 && bytes.slice(0, 8).every((value, index) => value === [
|
|
1616
1694
|
137,
|
|
1617
1695
|
80,
|
|
@@ -1621,14 +1699,18 @@ function detectImageMimeType(bytes) {
|
|
|
1621
1699
|
10,
|
|
1622
1700
|
26,
|
|
1623
1701
|
10
|
|
1624
|
-
][index])
|
|
1625
|
-
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255)
|
|
1702
|
+
][index]) && expected === "image/png") return expected;
|
|
1703
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
1704
|
+
if (expected === "image/jpeg") return expected;
|
|
1705
|
+
}
|
|
1626
1706
|
const ascii = (start, end) => new TextDecoder("ascii").decode(bytes.slice(start, end));
|
|
1627
|
-
if (bytes.length >= 12 && ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP")
|
|
1628
|
-
|
|
1707
|
+
if (bytes.length >= 12 && ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP") {
|
|
1708
|
+
if (expected === "image/webp") return expected;
|
|
1709
|
+
}
|
|
1710
|
+
throw new DesignError("INVALID_ARGUMENT", "Unsupported media type or content does not match its extension");
|
|
1629
1711
|
}
|
|
1630
1712
|
function safeFilename(value, mimeType) {
|
|
1631
|
-
const extension =
|
|
1713
|
+
const extension = `.${Object.entries(MEDIA_EXTENSIONS).find(([, type]) => type === mimeType)?.[0] ?? "bin"}`;
|
|
1632
1714
|
const headerSafe = value.normalize("NFC").replaceAll(/["\\/]/gu, "_");
|
|
1633
1715
|
const cleaned = Array.from(headerSafe, (character) => {
|
|
1634
1716
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
@@ -2510,10 +2592,18 @@ var PredictionClient = class {
|
|
|
2510
2592
|
const apiKey = validateApiKey(input.apiKey);
|
|
2511
2593
|
const maxAttempts = boundedAttempts(input.maxAttempts ?? 3);
|
|
2512
2594
|
const url = new URL(`/api/v1/tasks/${encodeURIComponent(taskId)}`, PREDICTION_ORIGIN);
|
|
2595
|
+
if (input.waitSeconds !== void 0) {
|
|
2596
|
+
if (!Number.isSafeInteger(input.waitSeconds) || input.waitSeconds < 1 || input.waitSeconds > 45) throw new DesignError("INVALID_ARGUMENT", "Wait duration must be an integer from 1 to 45 seconds");
|
|
2597
|
+
url.searchParams.set("wait", "true");
|
|
2598
|
+
url.searchParams.set("timeout", String(input.waitSeconds));
|
|
2599
|
+
}
|
|
2600
|
+
const totalTimeout = input.waitSeconds === void 0 ? this.#requestTimeoutMs : input.waitSeconds * 1e3 + 5e3;
|
|
2601
|
+
const overall = input.waitSeconds === void 0 ? void 0 : requestDeadline(input.signal, totalTimeout);
|
|
2513
2602
|
let lastFailure = null;
|
|
2514
2603
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2515
2604
|
input.signal?.throwIfAborted();
|
|
2516
|
-
|
|
2605
|
+
overall?.signal.throwIfAborted();
|
|
2606
|
+
const deadline = overall ?? requestDeadline(input.signal, this.#requestTimeoutMs);
|
|
2517
2607
|
try {
|
|
2518
2608
|
const response = await this.#fetch(url, {
|
|
2519
2609
|
method: "GET",
|
|
@@ -2531,7 +2621,7 @@ var PredictionClient = class {
|
|
|
2531
2621
|
retryAfterMs: failure.retryAfterMs
|
|
2532
2622
|
});
|
|
2533
2623
|
lastFailure = failure;
|
|
2534
|
-
await this.#waitBeforeRetry(attempt, failure.retryAfterMs, input.signal);
|
|
2624
|
+
await this.#waitBeforeRetry(attempt, failure.retryAfterMs, overall?.signal ?? input.signal);
|
|
2535
2625
|
continue;
|
|
2536
2626
|
}
|
|
2537
2627
|
let payload;
|
|
@@ -2543,6 +2633,10 @@ var PredictionClient = class {
|
|
|
2543
2633
|
}
|
|
2544
2634
|
const task = parsePredictionTask(payload, taskId);
|
|
2545
2635
|
if (task === null) throw new DesignError("UNEXPECTED_RESPONSE", "Task status response does not contain a valid task");
|
|
2636
|
+
if (input.waitSeconds !== void 0 && (task.status === "queued" || task.status === "running")) {
|
|
2637
|
+
const wait = asRecord$3(asRecord$3(asRecord$3(payload)?.data)?.wait);
|
|
2638
|
+
if (typeof wait?.timed_out !== "boolean" || typeof wait.elapsed_ms !== "number") throw new DesignError("UNEXPECTED_RESPONSE", "The task API does not support server-side wait; keep the task ID and do not resubmit");
|
|
2639
|
+
}
|
|
2546
2640
|
this.#log({
|
|
2547
2641
|
level: "info",
|
|
2548
2642
|
event: "design.task.read",
|
|
@@ -2555,22 +2649,30 @@ var PredictionClient = class {
|
|
|
2555
2649
|
} catch (caught) {
|
|
2556
2650
|
if (input.signal?.aborted === true) throw caught;
|
|
2557
2651
|
if (deadline.timedOut()) {
|
|
2558
|
-
if (attempt === maxAttempts) throw new DesignError("TASK_READ_FAILED", "Task status request timed out within the retry bound", {
|
|
2652
|
+
if (attempt === maxAttempts || input.waitSeconds !== void 0) throw new DesignError("TASK_READ_FAILED", "Task status request timed out within the retry bound", {
|
|
2559
2653
|
cause: caught,
|
|
2560
2654
|
status: 408
|
|
2561
2655
|
});
|
|
2562
2656
|
lastFailure = caught;
|
|
2563
|
-
await this.#waitBeforeRetry(attempt, null, input.signal);
|
|
2657
|
+
await this.#waitBeforeRetry(attempt, null, overall?.signal ?? input.signal);
|
|
2564
2658
|
continue;
|
|
2565
2659
|
}
|
|
2566
2660
|
if (caught instanceof DesignError) throw caught;
|
|
2567
2661
|
if (isAbortError$1(caught) || attempt === maxAttempts) throw new DesignError("TASK_READ_FAILED", "Task status could not be read within the retry bound", { cause: caught });
|
|
2568
2662
|
lastFailure = caught;
|
|
2569
|
-
await this.#waitBeforeRetry(attempt, null, input.signal);
|
|
2663
|
+
await this.#waitBeforeRetry(attempt, null, overall?.signal ?? input.signal);
|
|
2570
2664
|
}
|
|
2571
2665
|
}
|
|
2572
2666
|
throw new DesignError("TASK_READ_FAILED", "Task status could not be read within the retry bound", { cause: lastFailure });
|
|
2573
2667
|
}
|
|
2668
|
+
/** Wait for one bounded server observation; does not submit or sleep between snapshots. */
|
|
2669
|
+
async waitTask(input) {
|
|
2670
|
+
return this.readTask({
|
|
2671
|
+
...input,
|
|
2672
|
+
waitSeconds: input.timeoutSeconds ?? 30,
|
|
2673
|
+
maxAttempts: input.maxAttempts ?? 1
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2574
2676
|
async #waitBeforeRetry(attempt, retryAfter, signal) {
|
|
2575
2677
|
const delay = Math.min(MAX_INLINE_RETRY_DELAY_MS, Math.max(retryAfter ?? 0, 250 * 2 ** (attempt - 1)));
|
|
2576
2678
|
this.#log({
|
|
@@ -5480,6 +5582,11 @@ var DesignHostController = class {
|
|
|
5480
5582
|
#catalogNotice = null;
|
|
5481
5583
|
#catalogCredentialEpoch = null;
|
|
5482
5584
|
#pollCursor = 0;
|
|
5585
|
+
#pollInFlight;
|
|
5586
|
+
#pollAbort;
|
|
5587
|
+
#pollEpoch;
|
|
5588
|
+
#observationTail = Promise.resolve();
|
|
5589
|
+
#serializeObservation;
|
|
5483
5590
|
constructor(options) {
|
|
5484
5591
|
this.#repository = new DesignTaskRepository({ storage: options.storage });
|
|
5485
5592
|
this.#resolveCredential = options.resolveCredential;
|
|
@@ -5490,6 +5597,11 @@ var DesignHostController = class {
|
|
|
5490
5597
|
this.#rememberModel = options.rememberModel;
|
|
5491
5598
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
5492
5599
|
this.#now = options.now ?? Date.now;
|
|
5600
|
+
this.#serializeObservation = options.serializeObservation ?? ((operation) => {
|
|
5601
|
+
const run = this.#observationTail.then(operation);
|
|
5602
|
+
this.#observationTail = run.catch(() => void 0);
|
|
5603
|
+
return run;
|
|
5604
|
+
});
|
|
5493
5605
|
}
|
|
5494
5606
|
async handle(endpoint, payload, signal) {
|
|
5495
5607
|
signal?.throwIfAborted();
|
|
@@ -5509,32 +5621,59 @@ var DesignHostController = class {
|
|
|
5509
5621
|
if (!this.#isEnabled()) return false;
|
|
5510
5622
|
const credential = await this.#resolveCredential();
|
|
5511
5623
|
if (credential === void 0) return false;
|
|
5624
|
+
if (this.#pollEpoch !== credential.credentialEpoch) this.invalidateWaits();
|
|
5625
|
+
if (this.#pollInFlight !== void 0) return this.#pollInFlight;
|
|
5626
|
+
const abort = new AbortController();
|
|
5627
|
+
this.#pollAbort = abort;
|
|
5628
|
+
this.#pollEpoch = credential.credentialEpoch;
|
|
5629
|
+
const combined = signal === void 0 ? abort.signal : AbortSignal.any([signal, abort.signal]);
|
|
5630
|
+
const run = this.#waitRunning(credential, combined);
|
|
5631
|
+
this.#pollInFlight = run;
|
|
5632
|
+
try {
|
|
5633
|
+
return await run;
|
|
5634
|
+
} finally {
|
|
5635
|
+
if (this.#pollInFlight === run) {
|
|
5636
|
+
this.#pollInFlight = void 0;
|
|
5637
|
+
this.#pollAbort = void 0;
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
invalidateWaits() {
|
|
5642
|
+
this.#pollAbort?.abort();
|
|
5643
|
+
this.#pollAbort = void 0;
|
|
5644
|
+
this.#pollInFlight = void 0;
|
|
5645
|
+
this.#pollEpoch = void 0;
|
|
5646
|
+
}
|
|
5647
|
+
async #waitRunning(credential, signal) {
|
|
5512
5648
|
const eligible = (await this.#repository.listTasks()).filter((task) => task.taskId !== null && task.credentialEpoch === credential.credentialEpoch && !task.pollBlocked && (task.state === "queued" || task.state === "running" || task.state === "unknown")).filter((task) => task.nextPollAt <= this.#now()).sort((left, right) => left.createdAt - right.createdAt || String(left.taskId).localeCompare(String(right.taskId)));
|
|
5513
5649
|
const start = eligible.length === 0 ? 0 : this.#pollCursor % eligible.length;
|
|
5514
5650
|
const batch = circularSlice(eligible, start, 5);
|
|
5515
5651
|
this.#pollCursor = eligible.length === 0 ? 0 : (start + batch.length) % eligible.length;
|
|
5516
|
-
|
|
5517
|
-
if (!this.#isCredentialEpochCurrent(credential.credentialEpoch))
|
|
5652
|
+
await Promise.all(batch.map(async (record) => {
|
|
5653
|
+
if (!this.#isCredentialEpochCurrent(credential.credentialEpoch)) return;
|
|
5518
5654
|
try {
|
|
5519
|
-
const task = await new PredictionClient({ fetch: this.#fetch }).
|
|
5655
|
+
const task = await new PredictionClient({ fetch: this.#fetch }).waitTask({
|
|
5520
5656
|
taskId: record.taskId,
|
|
5521
5657
|
apiKey: credential.value,
|
|
5522
5658
|
maxAttempts: 1,
|
|
5523
5659
|
...signal === void 0 ? {} : { signal }
|
|
5524
5660
|
});
|
|
5525
|
-
await this.#
|
|
5661
|
+
await this.#serializeObservation(async () => {
|
|
5662
|
+
if (!signal.aborted && this.#isCredentialEpochCurrent(credential.credentialEpoch)) await this.#repository.recordTaskObserved(task);
|
|
5663
|
+
});
|
|
5526
5664
|
} catch (error) {
|
|
5527
5665
|
if (signal?.aborted === true) throw error;
|
|
5528
5666
|
const failure = classifyPollFailure(error, record.pollAttempt + 1, this.#now());
|
|
5529
|
-
await this.#
|
|
5667
|
+
await this.#serializeObservation(async () => {
|
|
5668
|
+
if (!signal.aborted && this.#isCredentialEpochCurrent(credential.credentialEpoch)) await this.#repository.recordPollFailure(record.taskId, failure);
|
|
5669
|
+
});
|
|
5530
5670
|
await this.#markUnauthorized(error, credential.credentialEpoch);
|
|
5531
5671
|
}
|
|
5532
|
-
}
|
|
5672
|
+
}));
|
|
5533
5673
|
return (await this.#repository.listTasks()).some((task) => task.credentialEpoch === credential.credentialEpoch && !task.pollBlocked && (task.state === "queued" || task.state === "running" || task.state === "unknown"));
|
|
5534
5674
|
}
|
|
5535
5675
|
async read(input, signal) {
|
|
5536
5676
|
const session = this.#session(input.sessionId);
|
|
5537
|
-
if (this.#isEnabled()) await this.pollRunning(signal);
|
|
5538
5677
|
const models = await this.#loadModelsSafely(signal);
|
|
5539
5678
|
if (session.selectedModelId === null && models.length > 0) {
|
|
5540
5679
|
const modelId = chooseDefaultModel(models, this.#getLastModel());
|
|
@@ -6215,13 +6354,14 @@ function stableJson(value) {
|
|
|
6215
6354
|
//#region src/host/agent-routing-context.ts
|
|
6216
6355
|
function createModellixRoutingMessage(state) {
|
|
6217
6356
|
const sections = [];
|
|
6357
|
+
const nonMediaWebRouting = state.webEnabled ? "use the Modellix Web tools for those questions" : "leave those questions to the Harness-native web_search or web_fetch tools when available";
|
|
6218
6358
|
if (state.webEnabled) sections.push({
|
|
6219
6359
|
name: "Web routing",
|
|
6220
6360
|
text: "Use modellix_web_search automatically for current, changing, external, or source-verification questions. Use modellix_web_fetch automatically when the user supplies a public URL or a Modellix search result needs full-page reading. Prefer these explicit Modellix tools over the native web_search and web_fetch tools, and never call both tool families for the same operation. Do not wait for the user to name a tool, do not browse when the user explicitly says not to, and never automatically repeat a failed or unknown Modellix Web request."
|
|
6221
6361
|
});
|
|
6222
6362
|
if (state.mediaEnabled) sections.push({
|
|
6223
6363
|
name: "Media routing",
|
|
6224
|
-
text:
|
|
6364
|
+
text: `Use Modellix media tools automatically only when the user intends to create, edit, transform, animate, upload, or inspect generated image, video, or audio work. Do not use media tools for documentation, pricing, general model-capability, or public API-reference questions; ${nonMediaWebRouting} unless the user explicitly requests a live Modellix Design model Schema. For media work, use modellix_media_list then modellix_media_schema when a compatible model is not already known. Treat every returned field description and allowed value as authoritative; do not guess, translate, or replace a published parameter format, and omit optional fields when the user did not request them. A request that refers to the previous/current work, conversation attachments, or prior Modellix result URLs, asks to change or animate it, or asks to preserve its composition, subject, identity, or structure is not a new text-to-media request: select an image-edit/image-to-image, image-to-video, or video-to-video model as appropriate, inspect its schema, and place the latest relevant Modellix result URL in the declared media input field. Never replace that source-dependent operation with text-to-image or text-to-video. Reuse usable URLs and upload local inputs with modellix_media_upload_file. After submitting, inspect the task at most once in the same turn. Hard response rule: while a task is nonterminal, ordinary assistant text may say only that the submission was accepted and that the result card and Modellix Design own the current status. It must not say that the task is queued, running, generating, processing, pending, or any equivalent current-state wording, because ordinary assistant text is immutable and becomes stale. Never launch a replacement or parallel generation while a matching task is active unless the user explicitly requests another result, and never repeat an unknown generation or upload outcome.`
|
|
6225
6365
|
});
|
|
6226
6366
|
if (sections.length === 0) return null;
|
|
6227
6367
|
return createUserMessage({
|
|
@@ -6562,7 +6702,7 @@ function createSchemaTool(controller) {
|
|
|
6562
6702
|
function createUploadFileTool(client, resolveCredential) {
|
|
6563
6703
|
return defineTool({
|
|
6564
6704
|
name: MODELLIX_MEDIA_UPLOAD_FILE_TOOL,
|
|
6565
|
-
description: "Upload
|
|
6705
|
+
description: "Upload an image attachment from this conversation or a supported image, video, or audio file inside the session workspace. Maximum 16 MiB (16777216 bytes). Supply exactly one of attachment_id or path. Reuse returned URLs and never retry an unknown upload outcome automatically. On quota errors, explicitly select unneeded files for deletion; never clean up automatically.",
|
|
6566
6706
|
parameters: {
|
|
6567
6707
|
attachment_id: {
|
|
6568
6708
|
type: "string",
|
|
@@ -6570,7 +6710,7 @@ function createUploadFileTool(client, resolveCredential) {
|
|
|
6570
6710
|
},
|
|
6571
6711
|
path: {
|
|
6572
6712
|
type: "string",
|
|
6573
|
-
description: "Image path inside the current session workspace."
|
|
6713
|
+
description: "Image, video, or audio path inside the current session workspace."
|
|
6574
6714
|
}
|
|
6575
6715
|
},
|
|
6576
6716
|
output: {
|
|
@@ -7859,7 +7999,7 @@ var PluginSettingsController = class {
|
|
|
7859
7999
|
//#endregion
|
|
7860
8000
|
//#region src/host/runtime.ts
|
|
7861
8001
|
const RPC_CHANNEL = "/modellix";
|
|
7862
|
-
const LLM_SETTINGS_NAMESPACE =
|
|
8002
|
+
const LLM_SETTINGS_NAMESPACE = "llm-pi-ai";
|
|
7863
8003
|
const LLM_OWNERSHIP_ROLLBACK_FAILED = "MODELLIX_LLM_OWNERSHIP_ROLLBACK_FAILED";
|
|
7864
8004
|
const LLM_MATERIALIZATION_RECOVERED = "MODELLIX_LLM_MATERIALIZATION_RECOVERED";
|
|
7865
8005
|
const LLM_MATERIALIZATION_RECOVERY_FAILED = "MODELLIX_LLM_MATERIALIZATION_RECOVERY_FAILED";
|
|
@@ -7899,6 +8039,7 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7899
8039
|
#credentialRecoveryRequestId = null;
|
|
7900
8040
|
#designPollTimer;
|
|
7901
8041
|
#disposeDesignTools;
|
|
8042
|
+
#disposeWebTools;
|
|
7902
8043
|
#closing = false;
|
|
7903
8044
|
#lifecycleAbort = new AbortController();
|
|
7904
8045
|
static async create(ctx) {
|
|
@@ -7953,6 +8094,7 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7953
8094
|
mutate: (operations, expectedRevision) => this.#ctx.settings.mutate(LLM_SETTINGS_NAMESPACE, operations, expectedRevision)
|
|
7954
8095
|
});
|
|
7955
8096
|
this.#design = new DesignHostController({
|
|
8097
|
+
serializeObservation: (operation) => this.enqueueDesignWrite(operation),
|
|
7956
8098
|
storage: designStorage,
|
|
7957
8099
|
resolveCredential: () => this.resolveUsableCredential(),
|
|
7958
8100
|
isCredentialEpochCurrent: (epoch) => epoch === this.#credential.credentialEpoch,
|
|
@@ -7972,11 +8114,13 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7972
8114
|
this.#ctx.effect(() => this.#settings.watch((next, previous) => {
|
|
7973
8115
|
if (this.#closing) return void 0;
|
|
7974
8116
|
this.#config = next;
|
|
8117
|
+
if (next.credentialEpoch !== previous.credentialEpoch || !next.services.design.enabled) this.#design.invalidateWaits();
|
|
7975
8118
|
this.#catalog.invalidate();
|
|
7976
8119
|
if (next.services.design.enabled !== previous.services.design.enabled) {
|
|
7977
8120
|
this.syncDesignTools(next.services.design.enabled);
|
|
7978
8121
|
if (next.services.design.enabled) this.scheduleDesignPoll(0);
|
|
7979
8122
|
}
|
|
8123
|
+
if (next.services.web.enabled !== previous.services.web.enabled) this.syncWebTools(next.services.web.enabled);
|
|
7980
8124
|
if (next.services.llm.enabled !== previous.services.llm.enabled || next.credentialEpoch !== previous.credentialEpoch) return this.enqueueWrite(() => this.reconcileLiveSettings());
|
|
7981
8125
|
}), "dsh-modellix: live settings snapshot");
|
|
7982
8126
|
this.#ctx.on("credentials/reference-updated", (updated) => {
|
|
@@ -8008,36 +8152,12 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
8008
8152
|
});
|
|
8009
8153
|
});
|
|
8010
8154
|
this.#ctx.effect(() => {
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
return hit === void 0 ? null : {
|
|
8018
|
-
apiKey: hit.value,
|
|
8019
|
-
credentialEpoch: hit.credentialEpoch
|
|
8020
|
-
};
|
|
8021
|
-
},
|
|
8022
|
-
getUserId: () => this.#userId,
|
|
8023
|
-
isCredentialEpochCurrent: (epoch) => epoch === this.#config.credentialEpoch,
|
|
8024
|
-
onCredentialRejected: async (epoch) => {
|
|
8025
|
-
if (epoch !== this.#config.credentialEpoch) return;
|
|
8026
|
-
this.markCredentialRejected(epoch);
|
|
8027
|
-
}
|
|
8028
|
-
});
|
|
8029
|
-
const disposeProviders = registerModellixWebProviderInstances(this.#ctx.web, providers);
|
|
8030
|
-
try {
|
|
8031
|
-
const disposeTools = registerModellixWebTools(this.#ctx, providers);
|
|
8032
|
-
return () => {
|
|
8033
|
-
disposeTools();
|
|
8034
|
-
disposeProviders();
|
|
8035
|
-
};
|
|
8036
|
-
} catch (error) {
|
|
8037
|
-
disposeProviders();
|
|
8038
|
-
throw error;
|
|
8039
|
-
}
|
|
8040
|
-
}, "dsh-modellix: Web providers and explicit Modellix tools");
|
|
8155
|
+
this.syncWebTools(this.#config.services.web.enabled);
|
|
8156
|
+
return () => {
|
|
8157
|
+
this.#disposeWebTools?.();
|
|
8158
|
+
this.#disposeWebTools = void 0;
|
|
8159
|
+
};
|
|
8160
|
+
}, "dsh-modellix: explicit Modellix Web tools");
|
|
8041
8161
|
this.#ctx.on("agent/session-start", ({ agent }) => {
|
|
8042
8162
|
const message = createModellixRoutingMessage({
|
|
8043
8163
|
mediaEnabled: this.#config.services.design.enabled && this.credentialIsUsable(),
|
|
@@ -8045,12 +8165,14 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
8045
8165
|
});
|
|
8046
8166
|
if (message !== null) agent.inject(message);
|
|
8047
8167
|
});
|
|
8048
|
-
|
|
8168
|
+
const connectionRpc = this.#ctx.connection.rpc;
|
|
8169
|
+
this.#ctx.effect(() => connectionRpc.handle(RPC_CHANNEL, (endpoint, payload, signal) => this.handleRpc(endpoint, payload, signal), { authority: "loopback" }), "dsh-modellix: loopback configuration and Design RPC");
|
|
8049
8170
|
this.#ctx.effect(() => {
|
|
8050
8171
|
this.scheduleDesignPoll(2e3);
|
|
8051
8172
|
return async () => {
|
|
8052
8173
|
this.#closing = true;
|
|
8053
8174
|
this.#lifecycleAbort.abort();
|
|
8175
|
+
this.#design.invalidateWaits();
|
|
8054
8176
|
this.#disposeDesignTools?.();
|
|
8055
8177
|
this.#disposeDesignTools = void 0;
|
|
8056
8178
|
if (this.#designPollTimer !== void 0) clearTimeout(this.#designPollTimer);
|
|
@@ -8709,7 +8831,10 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
8709
8831
|
handle: (endpoint, payload, signal) => {
|
|
8710
8832
|
const operationSignal = signal === void 0 ? this.#lifecycleAbort.signal : AbortSignal.any([signal, this.#lifecycleAbort.signal]);
|
|
8711
8833
|
if (designEndpointRequestsCredential(endpoint) && !this.credentialIsUsable()) this.requestCredentialRecovery();
|
|
8712
|
-
return this.enqueueDesignWrite(() => this.#design.handle(endpoint, payload, operationSignal))
|
|
8834
|
+
return this.enqueueDesignWrite(() => this.#design.handle(endpoint, payload, operationSignal)).then((snapshot) => {
|
|
8835
|
+
this.scheduleDesignPoll(0);
|
|
8836
|
+
return snapshot;
|
|
8837
|
+
});
|
|
8713
8838
|
}
|
|
8714
8839
|
});
|
|
8715
8840
|
return;
|
|
@@ -8719,11 +8844,39 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
8719
8844
|
this.#disposeDesignTools = void 0;
|
|
8720
8845
|
}
|
|
8721
8846
|
}
|
|
8847
|
+
syncWebTools(enabled) {
|
|
8848
|
+
if (enabled && this.#disposeWebTools === void 0) {
|
|
8849
|
+
const providers = createModellixWebProviders({
|
|
8850
|
+
isEnabled: () => this.#config.services.web.enabled,
|
|
8851
|
+
hasCredential: () => this.credentialIsUsable(),
|
|
8852
|
+
resolveCredential: async () => {
|
|
8853
|
+
const hit = await this.resolveUsableCredential();
|
|
8854
|
+
if (hit === void 0) this.requestCredentialRecovery();
|
|
8855
|
+
return hit === void 0 ? null : {
|
|
8856
|
+
apiKey: hit.value,
|
|
8857
|
+
credentialEpoch: hit.credentialEpoch
|
|
8858
|
+
};
|
|
8859
|
+
},
|
|
8860
|
+
getUserId: () => this.#userId,
|
|
8861
|
+
isCredentialEpochCurrent: (epoch) => epoch === this.#config.credentialEpoch,
|
|
8862
|
+
onCredentialRejected: async (epoch) => {
|
|
8863
|
+
if (epoch !== this.#config.credentialEpoch) return;
|
|
8864
|
+
this.markCredentialRejected(epoch);
|
|
8865
|
+
}
|
|
8866
|
+
});
|
|
8867
|
+
this.#disposeWebTools = registerModellixWebTools(this.#ctx, providers);
|
|
8868
|
+
return;
|
|
8869
|
+
}
|
|
8870
|
+
if (!enabled && this.#disposeWebTools !== void 0) {
|
|
8871
|
+
this.#disposeWebTools();
|
|
8872
|
+
this.#disposeWebTools = void 0;
|
|
8873
|
+
}
|
|
8874
|
+
}
|
|
8722
8875
|
scheduleDesignPoll(delayMs) {
|
|
8723
8876
|
if (this.#closing || this.#designPollTimer !== void 0) return;
|
|
8724
8877
|
this.#designPollTimer = setTimeout(() => {
|
|
8725
8878
|
this.#designPollTimer = void 0;
|
|
8726
|
-
this
|
|
8879
|
+
this.#design.pollRunning(this.#lifecycleAbort.signal).then((hasRunning) => {
|
|
8727
8880
|
if (hasRunning) this.scheduleDesignPoll(5e3);
|
|
8728
8881
|
}).catch(() => {
|
|
8729
8882
|
if (!this.#closing) this.scheduleDesignPoll(15e3);
|