create-windy 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +82 -2
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/embedding-server/src/southwind_embedding_server/app.py +27 -20
- package/template/apps/embedding-server/src/southwind_embedding_server/config.py +111 -4
- package/template/apps/embedding-server/src/southwind_embedding_server/contracts.py +13 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/pin_cli.py +62 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/readiness.py +259 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/readiness_contracts.py +221 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/readiness_http.py +91 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/run_service.py +23 -3
- package/template/apps/embedding-server/tests/test_config.py +13 -0
- package/template/apps/embedding-server/tests/test_readiness.py +202 -0
- package/template/apps/embedding-server/tests/test_run_http.py +76 -4
- package/template/apps/embedding-server/tests/test_run_service.py +3 -1
- package/template/apps/server/src/agent/drizzle-screenshot-intake-repository.integration.test.ts +3 -0
- package/template/apps/server/src/agent/run-facade.ts +2 -1
- package/template/apps/server/src/agent/run-route-screenshot-context.test.ts +13 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +2 -0
- package/template/apps/server/src/agent/screenshot-context-admission.test.ts +249 -0
- package/template/apps/server/src/agent/screenshot-context-attestation.ts +60 -0
- package/template/apps/server/src/agent/screenshot-intake-bootstrap.ts +2 -4
- package/template/apps/server/src/agent/screenshot-intake-test-support.ts +4 -0
- package/template/apps/server/src/agent/screenshot-intake-v2-validation.ts +24 -0
- package/template/apps/server/src/agent/screenshot-intake-v2.test.ts +7 -0
- package/template/apps/server/src/agent/screenshot-intake-v2.ts +8 -10
- package/template/apps/server/src/agent/screenshot-run-binding.ts +17 -2
- package/template/apps/server/src/agent/vision-readiness-pin.ts +6 -48
- package/template/apps/server/src/ai-runtime-readiness/deployment-pin.ts +69 -0
- package/template/apps/server/src/embedding/contracts.ts +6 -1
- package/template/apps/server/src/embedding/facade-readiness.test.ts +307 -0
- package/template/apps/server/src/embedding/facade.ts +8 -6
- package/template/apps/server/src/embedding/readiness-admission.ts +26 -0
- package/template/apps/server/src/embedding/remote-runtime.test.ts +85 -0
- package/template/apps/server/src/embedding/remote-runtime.ts +21 -0
- package/template/apps/server/src/embedding/run-fingerprint.test.ts +26 -0
- package/template/apps/server/src/embedding/run-fingerprint.ts +11 -2
- package/template/apps/server/src/module-host/ai-operation-port.test.ts +72 -0
- package/template/apps/server/src/module-host/ai-operation-port.ts +43 -0
- package/template/apps/server/src/module-host/ai-operation-speech-normalize.test.ts +186 -0
- package/template/docker-compose.yml +11 -0
- package/template/docs/architecture/ai-runtime-readiness.md +6 -0
- package/template/docs/platform/vision-ai-runtime.md +22 -1
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/package.json +1 -1
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/index.ts +1 -0
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/ai-operation-composition.ts +2 -0
- package/template/packages/modules/src/ai-screenshot-context.test.ts +72 -0
- package/template/packages/modules/src/ai-screenshot-context.ts +50 -0
- package/template/packages/modules/src/manifest.ts +8 -0
- package/template/packages/server-sdk/package.json +1 -1
- package/template/packages/shared/index.ts +3 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/screenshot-context-digest.test.ts +46 -0
- package/template/packages/shared/src/screenshot-context-digest.ts +72 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
parseAiScreenshotContextInputPointer,
|
|
4
|
+
type ModuleAiOperationDefinition,
|
|
5
|
+
} from "@southwind-ai/modules";
|
|
6
|
+
import {
|
|
7
|
+
computeScreenshotContextDigestV1,
|
|
8
|
+
isScreenshotContextDigestV1,
|
|
9
|
+
type JsonValue,
|
|
10
|
+
type ScreenshotContextDigestV1,
|
|
11
|
+
} from "@southwind-ai/shared";
|
|
12
|
+
|
|
13
|
+
/** null 表示 legacy 未 opt-in;undefined 表示已 opt-in 但上下文不能形成证明。 */
|
|
14
|
+
export async function expectedScreenshotContextDigest(
|
|
15
|
+
definition: ModuleAiOperationDefinition,
|
|
16
|
+
input: JsonValue,
|
|
17
|
+
): Promise<ScreenshotContextDigestV1 | null | undefined> {
|
|
18
|
+
const binding = definition.screenshotContext;
|
|
19
|
+
if (!binding) return null;
|
|
20
|
+
try {
|
|
21
|
+
const context = readPointer(
|
|
22
|
+
input,
|
|
23
|
+
parseAiScreenshotContextInputPointer(binding.contextInputPointer),
|
|
24
|
+
);
|
|
25
|
+
if (!isRecord(context)) return undefined;
|
|
26
|
+
return await computeScreenshotContextDigestV1(context);
|
|
27
|
+
} catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function sameScreenshotContextDigest(
|
|
33
|
+
actual: string,
|
|
34
|
+
expected: string | undefined,
|
|
35
|
+
): boolean {
|
|
36
|
+
if (
|
|
37
|
+
!isScreenshotContextDigestV1(actual) ||
|
|
38
|
+
!isScreenshotContextDigestV1(expected)
|
|
39
|
+
) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const actualBytes = Buffer.from(actual.slice("sha256:".length), "hex");
|
|
43
|
+
const expectedBytes = Buffer.from(expected.slice("sha256:".length), "hex");
|
|
44
|
+
return timingSafeEqual(actualBytes, expectedBytes);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readPointer(value: JsonValue, segments: readonly string[]): JsonValue {
|
|
48
|
+
let current = value;
|
|
49
|
+
for (const segment of segments) {
|
|
50
|
+
if (!isRecord(current) || !Object.hasOwn(current, segment)) {
|
|
51
|
+
throw new Error("Screenshot context input 缺失");
|
|
52
|
+
}
|
|
53
|
+
current = current[segment] as JsonValue;
|
|
54
|
+
}
|
|
55
|
+
return current;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isRecord(value: JsonValue): value is Record<string, JsonValue> {
|
|
59
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
60
|
+
}
|
|
@@ -9,10 +9,8 @@ import {
|
|
|
9
9
|
PersistentScreenshotIntakeProducer,
|
|
10
10
|
type ScreenshotPageRegistryV2,
|
|
11
11
|
} from "./persistent-screenshot-intake-producer.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
type ProjectAiScreenshotIntakeFactoryV2,
|
|
15
|
-
} from "./screenshot-intake-v2.js";
|
|
12
|
+
import { type ProjectAiScreenshotIntakeFactoryV2 } from "./screenshot-intake-durable.js";
|
|
13
|
+
import { ScreenshotIntakeRuntimeV2 } from "./screenshot-intake-v2.js";
|
|
16
14
|
|
|
17
15
|
export function createScreenshotIntakeRuntimeV2(input: {
|
|
18
16
|
readonly modules: readonly ModuleManifest[];
|
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
ModuleAiScreenshotPlanV2,
|
|
9
9
|
} from "@southwind-ai/server-sdk";
|
|
10
10
|
import type { RequestGuardContext } from "../guards.js";
|
|
11
|
+
import { sameScreenshotContextDigest } from "./screenshot-context-attestation.js";
|
|
12
|
+
import type { ScreenshotIntakeRecordV2 } from "./screenshot-intake-record.js";
|
|
11
13
|
|
|
12
14
|
const MAX_BYTES = 8 * 1_024 * 1_024;
|
|
13
15
|
const MAX_PIXELS = 20_000_000;
|
|
@@ -114,6 +116,28 @@ export function sameScreenshotDeployment(
|
|
|
114
116
|
);
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
export function canReserveScreenshot(
|
|
120
|
+
record: ScreenshotIntakeRecordV2,
|
|
121
|
+
operationKey: string,
|
|
122
|
+
deployment: AiRuntimeDeploymentV1 | undefined,
|
|
123
|
+
expectedContextDigest: string | null | undefined,
|
|
124
|
+
): record is ScreenshotIntakeRecordV2 & { artifact: ModuleAiArtifactRef } {
|
|
125
|
+
return Boolean(
|
|
126
|
+
record.artifact &&
|
|
127
|
+
(record.durable
|
|
128
|
+
? ["uploaded", "reserving", "bound"].includes(record.state)
|
|
129
|
+
: record.state === "uploaded") &&
|
|
130
|
+
record.operationKey === operationKey &&
|
|
131
|
+
(expectedContextDigest === null ||
|
|
132
|
+
sameScreenshotContextDigest(
|
|
133
|
+
record.contextDigest,
|
|
134
|
+
expectedContextDigest,
|
|
135
|
+
)) &&
|
|
136
|
+
deployment &&
|
|
137
|
+
sameScreenshotDeployment(record.deployment, deployment),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
117
141
|
export function sameScreenshotArtifact(
|
|
118
142
|
value: ModuleAiArtifactRef,
|
|
119
143
|
record: {
|
|
@@ -35,6 +35,7 @@ describe("Screenshot Intake V2", () => {
|
|
|
35
35
|
...deployment,
|
|
36
36
|
offersDigest: `sha256:${"b".repeat(64)}` as `sha256:${string}`,
|
|
37
37
|
},
|
|
38
|
+
declaration().contextDigest,
|
|
38
39
|
context,
|
|
39
40
|
),
|
|
40
41
|
).rejects.toThrow("Screenshot Intake");
|
|
@@ -50,6 +51,7 @@ describe("Screenshot Intake V2", () => {
|
|
|
50
51
|
second.intakeToken,
|
|
51
52
|
"evidence.image.inspect",
|
|
52
53
|
deployment,
|
|
54
|
+
declaration().contextDigest,
|
|
53
55
|
context,
|
|
54
56
|
);
|
|
55
57
|
expect(reserved.artifact.value).toBe("opaque-screenshot-artifact");
|
|
@@ -59,6 +61,7 @@ describe("Screenshot Intake V2", () => {
|
|
|
59
61
|
second.intakeToken,
|
|
60
62
|
"evidence.image.inspect",
|
|
61
63
|
deployment,
|
|
64
|
+
declaration().contextDigest,
|
|
62
65
|
context,
|
|
63
66
|
),
|
|
64
67
|
).rejects.toThrow("Screenshot Intake");
|
|
@@ -332,6 +335,10 @@ function manifest(): ModuleManifest {
|
|
|
332
335
|
minConcurrentRuns: 1,
|
|
333
336
|
minQueueDepth: 0,
|
|
334
337
|
},
|
|
338
|
+
screenshotContext: {
|
|
339
|
+
contractVersion: 1,
|
|
340
|
+
contextInputPointer: "/uiContext",
|
|
341
|
+
},
|
|
335
342
|
},
|
|
336
343
|
],
|
|
337
344
|
};
|
|
@@ -31,9 +31,9 @@ import {
|
|
|
31
31
|
bindingForScreenshot,
|
|
32
32
|
assertScreenshotOperation,
|
|
33
33
|
assertScreenshotProducerPlan,
|
|
34
|
+
canReserveScreenshot,
|
|
34
35
|
sameScreenshotArtifact,
|
|
35
36
|
sameScreenshotBinding,
|
|
36
|
-
sameScreenshotDeployment,
|
|
37
37
|
screenshotModuleName,
|
|
38
38
|
screenshotPurpose,
|
|
39
39
|
} from "./screenshot-intake-v2-validation.js";
|
|
@@ -42,8 +42,6 @@ const MAX_ENTRIES = 1_024;
|
|
|
42
42
|
const MAX_TTL_MS = 60_000;
|
|
43
43
|
const SWEEP_INTERVAL_MS = 10_000;
|
|
44
44
|
|
|
45
|
-
export type { ProjectAiScreenshotIntakeFactoryV2 } from "./screenshot-intake-durable.js";
|
|
46
|
-
|
|
47
45
|
export class ScreenshotIntakeRuntimeV2 {
|
|
48
46
|
readonly #records = new Map<string, ScreenshotIntakeRecordV2>();
|
|
49
47
|
readonly #sweeper: ReturnType<typeof setInterval>;
|
|
@@ -211,6 +209,7 @@ export class ScreenshotIntakeRuntimeV2 {
|
|
|
211
209
|
token: string,
|
|
212
210
|
operationKey: string,
|
|
213
211
|
deployment: AiRuntimeDeploymentV1 | undefined,
|
|
212
|
+
expectedContextDigest: string | null | undefined,
|
|
214
213
|
context: RequestGuardContext,
|
|
215
214
|
signal: AbortSignal = AbortSignal.timeout(10_000),
|
|
216
215
|
): Promise<{
|
|
@@ -226,13 +225,12 @@ export class ScreenshotIntakeRuntimeV2 {
|
|
|
226
225
|
context,
|
|
227
226
|
);
|
|
228
227
|
if (
|
|
229
|
-
!
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
!sameScreenshotDeployment(record.deployment, deployment)
|
|
228
|
+
!canReserveScreenshot(
|
|
229
|
+
record,
|
|
230
|
+
operationKey,
|
|
231
|
+
deployment,
|
|
232
|
+
expectedContextDigest,
|
|
233
|
+
)
|
|
236
234
|
) {
|
|
237
235
|
await this.cancel(record);
|
|
238
236
|
throw unavailable();
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AiRuntimeDeploymentV1,
|
|
3
3
|
ModuleAiArtifactRef,
|
|
4
|
+
ModuleAiJsonValue,
|
|
4
5
|
} from "@southwind-ai/server-sdk";
|
|
6
|
+
import type { ModuleAiOperationDefinition } from "@southwind-ai/modules";
|
|
5
7
|
import type { RequestGuardContext } from "../guards.js";
|
|
6
8
|
import { AgentRunFacadeError } from "./run-errors.js";
|
|
9
|
+
import { expectedScreenshotContextDigest } from "./screenshot-context-attestation.js";
|
|
7
10
|
import type { ScreenshotIntakeRuntimeV2 } from "./screenshot-intake-v2.js";
|
|
8
11
|
|
|
9
12
|
export interface ScreenshotRunBinding {
|
|
@@ -15,14 +18,26 @@ export interface ScreenshotRunBinding {
|
|
|
15
18
|
export async function reserveScreenshotForRun(
|
|
16
19
|
intake: ScreenshotIntakeRuntimeV2 | undefined,
|
|
17
20
|
token: string | undefined,
|
|
18
|
-
|
|
21
|
+
definition: ModuleAiOperationDefinition,
|
|
22
|
+
input: ModuleAiJsonValue,
|
|
19
23
|
deployment: AiRuntimeDeploymentV1 | undefined,
|
|
20
24
|
context: RequestGuardContext,
|
|
21
25
|
signal: AbortSignal,
|
|
22
26
|
): Promise<ScreenshotRunBinding | undefined> {
|
|
23
27
|
if (!token) return undefined;
|
|
24
28
|
if (!intake) throw unavailable();
|
|
25
|
-
|
|
29
|
+
const contextDigest = await expectedScreenshotContextDigest(
|
|
30
|
+
definition,
|
|
31
|
+
input,
|
|
32
|
+
);
|
|
33
|
+
return await intake.reserve(
|
|
34
|
+
token,
|
|
35
|
+
definition.key,
|
|
36
|
+
deployment,
|
|
37
|
+
contextDigest,
|
|
38
|
+
context,
|
|
39
|
+
signal,
|
|
40
|
+
);
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
function unavailable(): AgentRunFacadeError {
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
+
import type { AiRuntimeDeploymentV1 } from "@southwind-ai/server-sdk";
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "
|
|
3
|
+
assertAiRuntimeReadinessPin,
|
|
4
|
+
sameAiRuntimeDeployment,
|
|
5
|
+
} from "../ai-runtime-readiness/deployment-pin.js";
|
|
5
6
|
import { AgentRuntimeError } from "./run-contracts.js";
|
|
6
7
|
|
|
7
8
|
export function sameVisionReadinessDeployment(
|
|
8
9
|
actual: AiRuntimeDeploymentV1,
|
|
9
10
|
expected: AiRuntimeDeploymentV1,
|
|
10
11
|
): boolean {
|
|
11
|
-
return (
|
|
12
|
-
actual.key === expected.key &&
|
|
13
|
-
actual.identity === expected.identity &&
|
|
14
|
-
actual.releaseDigest === expected.releaseDigest &&
|
|
15
|
-
actual.offersDigest === expected.offersDigest
|
|
16
|
-
);
|
|
12
|
+
return sameAiRuntimeDeployment(actual, expected);
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
export function unavailableVisionReadiness(): AgentRuntimeError {
|
|
@@ -28,47 +24,9 @@ export async function assertVisionReadinessPin(
|
|
|
28
24
|
expected: AiRuntimeDeploymentV1,
|
|
29
25
|
health: () => Promise<Response>,
|
|
30
26
|
): Promise<void> {
|
|
31
|
-
const response = await boundedHealth(health);
|
|
32
|
-
if (response.status !== 200) throw unavailableVisionReadiness();
|
|
33
|
-
if (!response.headers.get("content-type")?.includes("application/json")) {
|
|
34
|
-
throw unavailableVisionReadiness();
|
|
35
|
-
}
|
|
36
|
-
const length = Number(response.headers.get("content-length") ?? "0");
|
|
37
|
-
if (!Number.isSafeInteger(length) || length < 0 || length > 64 * 1_024) {
|
|
38
|
-
throw unavailableVisionReadiness();
|
|
39
|
-
}
|
|
40
|
-
let snapshot: unknown;
|
|
41
27
|
try {
|
|
42
|
-
|
|
43
|
-
if (new TextEncoder().encode(text).byteLength > 64 * 1_024) {
|
|
44
|
-
throw new Error("health body too large");
|
|
45
|
-
}
|
|
46
|
-
snapshot = JSON.parse(text);
|
|
28
|
+
await assertAiRuntimeReadinessPin(expected, health);
|
|
47
29
|
} catch {
|
|
48
30
|
throw unavailableVisionReadiness();
|
|
49
31
|
}
|
|
50
|
-
const parsed = parseAiRuntimeReadinessV1(snapshot, new Date().toISOString());
|
|
51
|
-
if (
|
|
52
|
-
!parsed.ok ||
|
|
53
|
-
parsed.value.status !== "ready" ||
|
|
54
|
-
!sameVisionReadinessDeployment(parsed.value.deployment, expected)
|
|
55
|
-
) {
|
|
56
|
-
throw unavailableVisionReadiness();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function boundedHealth(
|
|
61
|
-
health: () => Promise<Response>,
|
|
62
|
-
): Promise<Response> {
|
|
63
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
64
|
-
try {
|
|
65
|
-
return await Promise.race([
|
|
66
|
-
health(),
|
|
67
|
-
new Promise<Response>((_, reject) => {
|
|
68
|
-
timeout = setTimeout(() => reject(unavailableVisionReadiness()), 5_000);
|
|
69
|
-
}),
|
|
70
|
-
]);
|
|
71
|
-
} finally {
|
|
72
|
-
if (timeout) clearTimeout(timeout);
|
|
73
|
-
}
|
|
74
32
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseAiRuntimeReadinessV1,
|
|
3
|
+
type AiRuntimeDeploymentV1,
|
|
4
|
+
} from "@southwind-ai/server-sdk";
|
|
5
|
+
|
|
6
|
+
export function sameAiRuntimeDeployment(
|
|
7
|
+
actual: AiRuntimeDeploymentV1,
|
|
8
|
+
expected: AiRuntimeDeploymentV1,
|
|
9
|
+
): boolean {
|
|
10
|
+
return (
|
|
11
|
+
actual.key === expected.key &&
|
|
12
|
+
actual.identity === expected.identity &&
|
|
13
|
+
actual.releaseDigest === expected.releaseDigest &&
|
|
14
|
+
actual.offersDigest === expected.offersDigest
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function assertAiRuntimeReadinessPin(
|
|
19
|
+
expected: AiRuntimeDeploymentV1,
|
|
20
|
+
health: () => Promise<Response>,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const response = await boundedHealth(health);
|
|
23
|
+
if (response.status !== 200) throw unavailable();
|
|
24
|
+
if (!response.headers.get("content-type")?.includes("application/json")) {
|
|
25
|
+
throw unavailable();
|
|
26
|
+
}
|
|
27
|
+
const length = Number(response.headers.get("content-length") ?? "0");
|
|
28
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > 64 * 1_024) {
|
|
29
|
+
throw unavailable();
|
|
30
|
+
}
|
|
31
|
+
let snapshot: unknown;
|
|
32
|
+
try {
|
|
33
|
+
const text = await response.text();
|
|
34
|
+
if (new TextEncoder().encode(text).byteLength > 64 * 1_024) {
|
|
35
|
+
throw new Error("health body too large");
|
|
36
|
+
}
|
|
37
|
+
snapshot = JSON.parse(text);
|
|
38
|
+
} catch {
|
|
39
|
+
throw unavailable();
|
|
40
|
+
}
|
|
41
|
+
const parsed = parseAiRuntimeReadinessV1(snapshot, new Date().toISOString());
|
|
42
|
+
if (
|
|
43
|
+
!parsed.ok ||
|
|
44
|
+
parsed.value.status !== "ready" ||
|
|
45
|
+
!sameAiRuntimeDeployment(parsed.value.deployment, expected)
|
|
46
|
+
) {
|
|
47
|
+
throw unavailable();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function boundedHealth(
|
|
52
|
+
health: () => Promise<Response>,
|
|
53
|
+
): Promise<Response> {
|
|
54
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
55
|
+
try {
|
|
56
|
+
return await Promise.race([
|
|
57
|
+
health(),
|
|
58
|
+
new Promise<Response>((_, reject) => {
|
|
59
|
+
timeout = setTimeout(() => reject(unavailable()), 5_000);
|
|
60
|
+
}),
|
|
61
|
+
]);
|
|
62
|
+
} finally {
|
|
63
|
+
if (timeout) clearTimeout(timeout);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function unavailable(): Error {
|
|
68
|
+
return new Error("AI Runtime readiness pin mismatch");
|
|
69
|
+
}
|
|
@@ -4,7 +4,10 @@ import type {
|
|
|
4
4
|
AiProviderDataPolicy,
|
|
5
5
|
AiProviderEgressPolicy,
|
|
6
6
|
} from "@southwind-ai/modules";
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
AiRuntimeDeploymentV1,
|
|
9
|
+
ModuleAiJsonValue,
|
|
10
|
+
} from "@southwind-ai/server-sdk";
|
|
8
11
|
|
|
9
12
|
export type EmbeddingRuntimeItem =
|
|
10
13
|
| { readonly id: string; readonly text: string }
|
|
@@ -36,6 +39,8 @@ export interface EmbeddingRuntimeOperation {
|
|
|
36
39
|
};
|
|
37
40
|
readonly providerEgress: AiProviderEgressPolicy;
|
|
38
41
|
readonly providerDataPolicy?: AiProviderDataPolicy;
|
|
42
|
+
/** Host-projected immutable deployment facts; never supplied by a Module. */
|
|
43
|
+
readonly readinessPin?: AiRuntimeDeploymentV1;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
export interface EmbeddingBatchResponse {
|