@southwind-ai/shared 0.2.5 → 0.2.7

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/index.ts CHANGED
@@ -23,5 +23,8 @@ export * from "./src/rbac.js";
23
23
  export * from "./src/ruoyi.js";
24
24
  export * from "./src/scheduler.js";
25
25
  export * from "./src/search-provider.js";
26
+ // @windy-module system.agent begin
27
+ export * from "./src/screenshot-context-digest.js";
28
+ // @windy-module system.agent end
26
29
  export * from "./src/stable-key.js";
27
30
  export * from "./src/workflow.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/shared",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -93,6 +93,18 @@ export interface AiRuntimeRunUsage {
93
93
  totalTokens: number | null;
94
94
  }
95
95
 
96
+ /**
97
+ * 一个 Run 中实际观察到的模型与 deployment 组合。
98
+ *
99
+ * `model` 优先采用 Provider 回报的模型名;Provider 未回报时才保留请求模型。
100
+ * 排序遵循该组合首次出现的 Attempt 顺序,避免在报表投影时伪造执行顺序。
101
+ */
102
+ export interface AiRuntimeRunModelDeployment {
103
+ deploymentKey: string;
104
+ model: string;
105
+ attemptCount: number;
106
+ }
107
+
96
108
  export interface AiRuntimeRunListItem {
97
109
  runId: string;
98
110
  operationKey: string;
@@ -102,6 +114,8 @@ export interface AiRuntimeRunListItem {
102
114
  finishedAt: ISODateTime | null;
103
115
  durationMs: number | null;
104
116
  attemptCount: number;
117
+ modelDeployments: AiRuntimeRunModelDeployment[];
118
+ /** @deprecated 请使用包含 deployment 的 modelDeployments。 */
105
119
  models: string[];
106
120
  cacheStatus: AiRuntimeCacheStatus;
107
121
  usage: AiRuntimeRunUsage;
@@ -112,6 +126,11 @@ export interface AiRuntimeAttemptDetail extends AiRuntimeRunUsage {
112
126
  sequence: number;
113
127
  toolRound: number;
114
128
  deploymentKey: string;
129
+ /** 请求发送给 Provider 的模型名。 */
130
+ requestedModel: string;
131
+ /** Provider 响应中实际回报的模型名;缺失时不能推断。 */
132
+ reportedModel: string | null;
133
+ /** @deprecated 优先使用 reportedModel,缺失时使用 requestedModel。 */
115
134
  model: string;
116
135
  outcome: AiRuntimeAttemptOutcome;
117
136
  cacheStatus: AiRuntimeCacheStatus;
@@ -0,0 +1,72 @@
1
+ import type { JsonValue } from "./json.js";
2
+
3
+ const MAX_DEPTH = 32;
4
+ const MAX_NODES = 4_096;
5
+ const MAX_CANONICAL_BYTES = 256 * 1_024;
6
+ const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
7
+
8
+ export type ScreenshotContextDigestV1 = `sha256:${string}`;
9
+
10
+ /** Web/Host 共用的稳定 JSON:object key 排序,array 顺序与 JSON 数值语义保持不变。 */
11
+ export function canonicalizeScreenshotContextV1(value: JsonValue): string {
12
+ let nodes = 0;
13
+ const visit = (item: JsonValue, depth: number): string => {
14
+ nodes += 1;
15
+ if (nodes > MAX_NODES || depth > MAX_DEPTH) {
16
+ throw new Error("Screenshot context 超过稳定摘要边界");
17
+ }
18
+ if (
19
+ item === null ||
20
+ typeof item === "boolean" ||
21
+ typeof item === "string"
22
+ ) {
23
+ return JSON.stringify(item);
24
+ }
25
+ if (typeof item === "number") {
26
+ if (!Number.isFinite(item))
27
+ throw new Error("Screenshot context 数值无效");
28
+ return JSON.stringify(item);
29
+ }
30
+ if (Array.isArray(item)) {
31
+ if (item.length > MAX_NODES) {
32
+ throw new Error("Screenshot context 数组超过稳定摘要边界");
33
+ }
34
+ return `[${item.map((entry) => visit(entry, depth + 1)).join(",")}]`;
35
+ }
36
+ const prototype = Object.getPrototypeOf(item);
37
+ if (prototype !== Object.prototype && prototype !== null) {
38
+ throw new Error("Screenshot context 必须是 plain JSON");
39
+ }
40
+ const keys = Object.keys(item).sort();
41
+ return `{${keys
42
+ .map(
43
+ (key) =>
44
+ `${JSON.stringify(key)}:${visit(item[key] as JsonValue, depth + 1)}`,
45
+ )
46
+ .join(",")}}`;
47
+ };
48
+ const canonical = visit(value, 0);
49
+ if (new TextEncoder().encode(canonical).byteLength > MAX_CANONICAL_BYTES) {
50
+ throw new Error("Screenshot context 超过稳定摘要字节边界");
51
+ }
52
+ return canonical;
53
+ }
54
+
55
+ export async function computeScreenshotContextDigestV1(
56
+ value: JsonValue,
57
+ ): Promise<ScreenshotContextDigestV1> {
58
+ const bytes = new TextEncoder().encode(
59
+ canonicalizeScreenshotContextV1(value),
60
+ );
61
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
62
+ const hex = [...new Uint8Array(digest)]
63
+ .map((byte) => byte.toString(16).padStart(2, "0"))
64
+ .join("");
65
+ return `sha256:${hex}`;
66
+ }
67
+
68
+ export function isScreenshotContextDigestV1(
69
+ value: unknown,
70
+ ): value is ScreenshotContextDigestV1 {
71
+ return typeof value === "string" && DIGEST_PATTERN.test(value);
72
+ }