@77-zhike/app-sdk 0.2.0-d7b1.2 → 0.2.0-d7b1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +281 -0
- package/package.json +17 -3
- package/src/cli/api-client.js +4 -0
- package/src/cli/browser-auth.js +64 -18
- package/src/cli/credential-store.js +21 -0
- package/src/cli/development-logs.js +5 -2
- package/src/cli/index.js +59 -42
- package/src/cli/local-commands.js +6 -3
- package/src/cli/logs.js +56 -0
- package/src/cli/project-init.js +111 -179
- package/src/cli/windows-credential-bridge.ps1 +131 -0
- package/src/cli/windows-credential-store.js +112 -0
- package/src/client/analysis.d.ts +4 -0
- package/src/client/analysis.js +12 -0
- package/src/client/destinations.d.ts +25 -0
- package/src/client/destinations.js +70 -0
- package/src/client/documents.d.ts +21 -0
- package/src/client/documents.js +39 -0
- package/src/client/external-url.js +9 -0
- package/src/client/index.d.ts +225 -3
- package/src/client/index.js +75 -3
- package/src/client/interactions.d.ts +23 -3
- package/src/client/interactions.js +53 -3
- package/src/client/points.generated.js +56 -0
- package/src/config.js +11 -7
- package/src/forms/array-contract.d.ts +32 -0
- package/src/forms/array-contract.js +138 -0
- package/src/forms/components.generated.js +2 -2
- package/src/forms/factory.js +21 -7
- package/src/forms/field-array.d.ts +16 -0
- package/src/forms/field-array.js +52 -0
- package/src/forms/index.d.ts +74 -3
- package/src/forms/index.js +2 -0
- package/src/forms/options.d.ts +7 -2
- package/src/forms/options.js +8 -1
- package/src/forms/record-options.d.ts +28 -0
- package/src/forms/record-options.js +92 -0
- package/src/forms/schema.d.ts +73 -9
- package/src/forms/schema.js +128 -0
- package/src/forms/state.d.ts +30 -0
- package/src/forms/state.js +82 -0
- package/src/forms/validation.d.ts +9 -0
- package/src/forms/validation.js +81 -0
- package/src/forms/values.d.ts +5 -0
- package/src/forms/values.js +38 -0
- package/src/jsx-dev-runtime.d.ts +2 -0
- package/src/jsx-dev-runtime.js +1 -0
- package/src/jsx-runtime.d.ts +2 -0
- package/src/jsx-runtime.js +1 -0
- package/src/server/agents/index.d.ts +62 -0
- package/src/server/agents/index.js +40 -0
- package/src/server/index.d.ts +60 -5
- package/src/server/index.js +43 -0
- package/src/server/steps/index.d.ts +71 -0
- package/src/server/steps/index.js +15 -0
- package/src/server/triggers/index.d.ts +68 -0
- package/src/server/triggers/index.js +27 -0
- package/src/server/workflow-blocks/index.d.ts +72 -0
- package/src/server/workflow-blocks/index.js +32 -0
- package/src/toolchain/agents/discovery.js +126 -0
- package/src/toolchain/agents/exposure.js +31 -0
- package/src/toolchain/agents/manifest.js +84 -0
- package/src/toolchain/ai-capabilities.js +69 -0
- package/src/toolchain/allowed-packages.json +7 -0
- package/src/toolchain/dependency-policy.js +181 -0
- package/src/toolchain/index.js +0 -88
- package/src/toolchain/real-build.js +135 -27
- package/src/toolchain/source-discovery.js +330 -81
- package/src/toolchain/workflow-blocks/discovery.js +133 -0
- package/src/toolchain/workflow-steps/manifest.js +99 -0
- package/src/toolchain/workflow-triggers/manifest.js +47 -0
- package/src/ui/components.generated.js +4 -1
- package/src/ui/conversation.d.ts +81 -0
- package/src/ui/conversation.js +82 -0
- package/src/ui/data-table-model.d.ts +14 -0
- package/src/ui/data-table-model.js +98 -0
- package/src/ui/data-table.d.ts +70 -0
- package/src/ui/data-table.js +74 -0
- package/src/ui/events.d.ts +10 -0
- package/src/ui/factory.js +87 -3
- package/src/ui/index.d.ts +351 -8
- package/src/ui/index.js +1 -1
- package/src/ui/jsx.generated.d.ts +359 -0
- package/src/ui/native-dom.d.ts +49 -0
- package/src/ui/native-dom.js +44 -0
- package/src/ui/scroll-viewport.d.ts +22 -0
- package/src/ui/scroll-viewport.js +23 -0
- package/src/ui/slider.d.ts +27 -0
- package/src/ui/slider.js +26 -0
- package/src/ui/style.d.ts +5 -0
- package/src/ui/style.generated.js +2 -0
- package/src/ui/style.js +15 -0
- package/src/ui/text-selection.d.ts +22 -0
- package/src/ui/text-selection.js +23 -0
- package/src/ui/validation.js +86 -0
- package/src/cli/validate-bundle.js +0 -36
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { isPlainDate, isDateTime } from './values.js';
|
|
2
|
+
import { recordObjectsForField } from './record-options.js';
|
|
3
|
+
import { freezeFormState } from './state.js';
|
|
4
|
+
import { createValidationCallback } from './validation.js';
|
|
5
|
+
|
|
6
|
+
import { validFieldKey, assertArrayCapacity } from './array-contract.js';
|
|
7
|
+
|
|
8
|
+
const descriptors = new WeakMap();
|
|
9
|
+
const fail = () => { throw new Error('runtime_form_schema_invalid'); };
|
|
10
|
+
const message = value => {
|
|
11
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 300) fail();
|
|
12
|
+
return value;
|
|
13
|
+
};
|
|
14
|
+
function builder(field, validators = []) {
|
|
15
|
+
field = freezeFormState(field);
|
|
16
|
+
const next = (patch, checks = validators) => builder({ ...field, ...patch }, checks);
|
|
17
|
+
const rule = (key, value, options) => {
|
|
18
|
+
const messages = { ...field.messages };
|
|
19
|
+
if (options !== undefined) {
|
|
20
|
+
if (!options || Object.keys(options).some(key => key !== 'message')) fail();
|
|
21
|
+
messages[key] = message(options.message);
|
|
22
|
+
} else delete messages[key];
|
|
23
|
+
return next({ [field.type === 'array' ? `${key}Items` : field.type === 'string' ? `${key}Length` : key]: value, messages });
|
|
24
|
+
};
|
|
25
|
+
const methods = {
|
|
26
|
+
label(value) { return next({ label: value }); },
|
|
27
|
+
required(value) { return next({ required: true, messages: { ...field.messages, ...(value === undefined ? {} : { required: message(value) }) } }); },
|
|
28
|
+
optional() { return next({ required: false }); },
|
|
29
|
+
custom(validator) {
|
|
30
|
+
if (typeof validator !== 'function' || validators.length >= 4) fail();
|
|
31
|
+
return next({}, [...validators, validator]);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
if (['array', 'string', 'number', 'plainDate', 'dateTime'].includes(field.type)) {
|
|
35
|
+
methods.min = (value, options) => rule('min', value, options);
|
|
36
|
+
methods.max = (value, options) => rule('max', value, options);
|
|
37
|
+
}
|
|
38
|
+
if (field.type === 'array') { delete methods.required; delete methods.optional; }
|
|
39
|
+
if (field.type === 'string') for (const format of ['email', 'url']) methods[format] = options => {
|
|
40
|
+
const messages = { ...field.messages };
|
|
41
|
+
if (options === undefined) delete messages.format;
|
|
42
|
+
else { if (!options || Object.keys(options).some(key => key !== 'message')) fail(); messages.format = message(options.message); }
|
|
43
|
+
return next({ format, messages });
|
|
44
|
+
};
|
|
45
|
+
const result = Object.freeze({ ...field, ...methods });
|
|
46
|
+
descriptors.set(result, { field, validators });
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
export const Forms = Object.freeze({
|
|
50
|
+
schema(fields) {
|
|
51
|
+
if (!fields || typeof fields !== 'object' || Array.isArray(fields)) fail();
|
|
52
|
+
if (Object.values(fields).some(field => !descriptors.has(field))) fail();
|
|
53
|
+
return Object.freeze({ fields: Object.freeze({ ...fields }) });
|
|
54
|
+
},
|
|
55
|
+
...Object.fromEntries(['string', 'number', 'boolean', 'plainDate', 'dateTime'].map(type => [type, () => builder({ type })])),
|
|
56
|
+
enum: options => builder({ type: 'enum', options: Array.isArray(options) ? options.map(option => Object.freeze({ ...option })) : options }),
|
|
57
|
+
array(fields) {
|
|
58
|
+
if (!fields || typeof fields !== 'object' || Array.isArray(fields) || !Object.keys(fields).length || Object.keys(fields).length > 12 || Object.values(fields).some(field => !descriptors.has(field) || field.type === 'array')) fail();
|
|
59
|
+
return builder({ type: 'array', minItems: 0, maxItems: 50, item: { type: 'object', fields: { ...fields } } });
|
|
60
|
+
},
|
|
61
|
+
record: objectSlug => builder({ type: 'record', objectSlug }),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/** 只编译描述与回调;输入规则由 Host 单一校验器执行。 */
|
|
65
|
+
export function compileForm(options, formId, bindCallback = callback => callback) {
|
|
66
|
+
const { schema, validate, validationMode = 'onBlur' } = options;
|
|
67
|
+
if (!schema || !schema.fields || typeof schema.fields !== 'object' || Array.isArray(schema.fields) || Object.keys(schema).some(key => key !== 'fields')
|
|
68
|
+
|| Object.values(schema.fields).filter(field => field.type !== 'array').length > 30 || Object.values(schema.fields).filter(field => field.type === 'array').length > 3 || !['onBlur', 'onChange', 'onSubmit'].includes(validationMode)
|
|
69
|
+
|| (validate !== undefined && typeof validate !== 'function')) fail();
|
|
70
|
+
const fields = {}, validators = {};
|
|
71
|
+
for (const [name, input] of Object.entries(schema.fields)) {
|
|
72
|
+
if (!validFieldKey(name)) fail();
|
|
73
|
+
const descriptor = descriptors.get(input);
|
|
74
|
+
const source = descriptor?.field ?? input;
|
|
75
|
+
if (source?.type === 'array') {
|
|
76
|
+
if (Object.keys(source).some(key => !['type', 'label', 'minItems', 'maxItems', 'item', 'messages'].includes(key)) || typeof source.label !== 'string' || !source.label.trim() || source.label.length > 200
|
|
77
|
+
|| !Number.isSafeInteger(source.minItems) || source.minItems < 0 || !Number.isSafeInteger(source.maxItems) || source.maxItems < 1 || source.maxItems > 50 || source.minItems > source.maxItems
|
|
78
|
+
|| source.item?.type !== 'object' || Object.keys(source.item).some(key => !['type', 'fields'].includes(key)) || !source.item.fields || Object.values(source.item.fields).some(field => field.type === 'array')
|
|
79
|
+
|| !Object.keys(source.item.fields).length || Object.keys(source.item.fields).length > 12) fail();
|
|
80
|
+
const child = compileForm({ schema: { fields: source.item.fields } }, formId, bindCallback);
|
|
81
|
+
fields[name] = { ...source, item: { type: 'object', fields: child.schema.fields } };
|
|
82
|
+
if (source.messages) { if (Object.keys(source.messages).some(key => !['min', 'max'].includes(key))) fail(); Object.values(source.messages).forEach(message); }
|
|
83
|
+
for (const [key, checks] of Object.entries(child.validators)) validators[`${name}[*].${key}`] = checks;
|
|
84
|
+
if (descriptor?.validators.length) validators[name] = descriptor.validators.map(bindCallback);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const field = structuredClone(source);
|
|
88
|
+
if (!field || !['string', 'boolean', 'enum', 'number', 'plainDate', 'dateTime', 'record'].includes(field.type)
|
|
89
|
+
|| typeof field.label !== 'string' || descriptor && !field.label.trim() || field.label.length > 200) fail();
|
|
90
|
+
const allowed = ['type', 'label', 'required', 'messages', ...(field.type === 'string' ? ['minLength', 'maxLength', 'format'] : field.type === 'enum' ? ['options'] : field.type === 'record' ? ['objectSlug', 'objects'] : ['number', 'plainDate', 'dateTime'].includes(field.type) ? ['min', 'max'] : [])];
|
|
91
|
+
if (Object.keys(field).some(key => !allowed.includes(key)) || (field.required !== undefined && typeof field.required !== 'boolean')) fail();
|
|
92
|
+
if (field.messages !== undefined) {
|
|
93
|
+
if (!field.messages || typeof field.messages !== 'object' || Array.isArray(field.messages)
|
|
94
|
+
|| Object.keys(field.messages).some(key => !['required', 'min', 'max', 'format'].includes(key))) fail();
|
|
95
|
+
Object.values(field.messages).forEach(message);
|
|
96
|
+
}
|
|
97
|
+
if (field.type === 'record') recordObjectsForField(field);
|
|
98
|
+
if (field.type === 'enum') {
|
|
99
|
+
if (!Array.isArray(field.options) || !field.options.length || field.options.length > 100) fail();
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
for (const choice of field.options) {
|
|
102
|
+
if (!choice || Object.keys(choice).some(key => !['value', 'label', 'description'].includes(key)) || typeof choice.value !== 'string' || !choice.value || choice.value.length > 200 || seen.has(choice.value) || typeof choice.label !== 'string' || choice.label.length > 200) fail();
|
|
103
|
+
if (choice.description !== undefined && (typeof choice.description !== 'string' || choice.description.length > 300)) fail();
|
|
104
|
+
seen.add(choice.value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const low = field.type === 'string' ? 'minLength' : 'min', high = field.type === 'string' ? 'maxLength' : 'max';
|
|
108
|
+
const valid = value => field.type === 'string' ? Number.isSafeInteger(value) && value >= 0 && value <= 10000
|
|
109
|
+
: field.type === 'number' ? typeof value === 'number' && Number.isFinite(value)
|
|
110
|
+
: field.type === 'plainDate' ? isPlainDate(value) : isDateTime(value);
|
|
111
|
+
for (const key of [low, high]) if (field[key] !== undefined && (!valid(field[key]) || key === 'maxLength' && field[key] === 0)) fail();
|
|
112
|
+
const comparable = value => field.type === 'dateTime' ? Date.parse(value) : value;
|
|
113
|
+
if (field[low] !== undefined && field[high] !== undefined && comparable(field[low]) > comparable(field[high])) fail();
|
|
114
|
+
if (field.format !== undefined && !['email', 'url'].includes(field.format)) fail();
|
|
115
|
+
fields[name] = field;
|
|
116
|
+
if (descriptor?.validators.length) validators[name] = descriptor.validators.map(bindCallback);
|
|
117
|
+
}
|
|
118
|
+
const wire = freezeFormState({ fields });
|
|
119
|
+
const validation = { fields: Object.keys(validators), form: Boolean(validate), mode: validationMode };
|
|
120
|
+
const remote = Boolean(validation.fields.length || validate);
|
|
121
|
+
const configured = remote || options.validationMode !== undefined || Object.values(schema.fields).some(field => descriptors.has(field));
|
|
122
|
+
if (options.initialValues !== undefined) assertArrayCapacity(fields, options.initialValues);
|
|
123
|
+
const result = { schema: wire, ...(configured ? { validation } : {}), ...(remote ? {
|
|
124
|
+
onValidate: createValidationCallback(formId, fields, validators, validate ? bindCallback(validate) : undefined),
|
|
125
|
+
} : {}) };
|
|
126
|
+
Object.defineProperty(result, 'validators', { value: validators });
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { FormSchema, FormValues, FormFieldErrors, FormArrayErrors } from './schema.js';
|
|
3
|
+
export type FormStateSelection<S extends FormSchema> = {
|
|
4
|
+
values?: true | readonly (keyof S['fields'] & string)[];
|
|
5
|
+
arrayErrors?: true | readonly (keyof FormArrayErrors<S> & string)[];
|
|
6
|
+
errors?: true | readonly (keyof S['fields'] & string)[];
|
|
7
|
+
submitting?: boolean;
|
|
8
|
+
validating?: boolean;
|
|
9
|
+
validatingFields?: true | readonly (keyof S['fields'] & string)[];
|
|
10
|
+
formError?: boolean;
|
|
11
|
+
};
|
|
12
|
+
type Names<S extends FormSchema, T> = T extends true ? keyof FormValues<S> : T extends readonly (infer K)[] ? Extract<K, keyof FormValues<S>> : never;
|
|
13
|
+
type ReadonlyValue<T> = T extends object ? { readonly [K in keyof T]: ReadonlyValue<T[K]> } : T;
|
|
14
|
+
export type SelectedFormState<S extends FormSchema, Selection extends FormStateSelection<S>> =
|
|
15
|
+
(Selection extends { values: infer T } ? { readonly values: ReadonlyValue<Pick<FormValues<S>, Names<S, T>>> } : {})
|
|
16
|
+
& (Selection extends { errors: infer T } ? { readonly errors: ReadonlyValue<Pick<FormFieldErrors<S>, Names<S, T>>> } : {})
|
|
17
|
+
& (Selection extends { arrayErrors: infer T } ? { readonly arrayErrors: Readonly<Pick<FormArrayErrors<S>, Extract<Names<S, T>, keyof FormArrayErrors<S>>>> } : {})
|
|
18
|
+
& (Selection extends { submitting: true } ? { readonly submitting: boolean } : {})
|
|
19
|
+
& (Selection extends { validating: true } ? { readonly validating: boolean } : {})
|
|
20
|
+
& (Selection extends { formError: true } ? { readonly formError: string | null } : {})
|
|
21
|
+
& (Selection extends { validatingFields: infer T } ? { readonly validatingFields: ReadonlyValue<Pick<FormFieldErrors<S, boolean>, Names<S, T>>> } : {});
|
|
22
|
+
export type WithStateComponent<S extends FormSchema> = <const Selection extends FormStateSelection<S>>(props: {
|
|
23
|
+
selection: Selection;
|
|
24
|
+
children: (state: SelectedFormState<S, Selection>) => ReactNode;
|
|
25
|
+
}) => ReactNode;
|
|
26
|
+
|
|
27
|
+
export type StateSelection = { arrayErrors?: true | readonly string[]; values?: true | readonly string[]; errors?: true | readonly string[]; submitting?: boolean; validating?: boolean; validatingFields?: true | readonly string[]; formError?: boolean };
|
|
28
|
+
export function selectedNames(selection: true | readonly string[] | undefined, fields: Record<string, unknown>): string[];
|
|
29
|
+
export function assertFormStateSelection(selection: unknown, fields: Record<string, unknown>): StateSelection;
|
|
30
|
+
export function assertFormStateSnapshot(snapshot: unknown, formId: string, fields: Record<string, unknown>, selection: StateSelection): unknown;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { assertFieldValue, assertErrorTree, assertArrayErrors, normalizeFormValues, formBytes, ARRAY_LIMITS } from './array-contract.js';
|
|
2
|
+
import { assertJsonValue } from '../ui/validation.js';
|
|
3
|
+
const fail = () => { throw new Error('runtime_form_state_invalid'); };
|
|
4
|
+
const only = (value, keys) => {
|
|
5
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(key => !keys.includes(key))) fail();
|
|
6
|
+
};
|
|
7
|
+
export function selectedNames(selection, fields) {
|
|
8
|
+
return selection === true ? Object.keys(fields) : Array.isArray(selection) ? selection : [];
|
|
9
|
+
}
|
|
10
|
+
export function assertFormStateSelection(selection, fields) {
|
|
11
|
+
only(selection, ['values', 'errors', 'submitting', 'validating', 'validatingFields', 'arrayErrors', 'formError']);
|
|
12
|
+
for (const kind of ['values', 'errors', 'validatingFields', 'arrayErrors']) {
|
|
13
|
+
const names = selection[kind];
|
|
14
|
+
if (names === undefined || names === true) continue;
|
|
15
|
+
if (!Array.isArray(names) || names.length > 33 || new Set(names).size !== names.length || names.some(name => typeof name !== 'string' || !Object.hasOwn(fields, name))) fail();
|
|
16
|
+
if (kind === 'arrayErrors' && names.some(name => fields[name].type !== 'array')) fail();
|
|
17
|
+
}
|
|
18
|
+
for (const key of ['submitting', 'validating', 'formError']) if (selection[key] !== undefined && typeof selection[key] !== 'boolean') fail();
|
|
19
|
+
return selection;
|
|
20
|
+
}
|
|
21
|
+
export function assertFormStateSnapshot(snapshot, formId, fields, selection) {
|
|
22
|
+
assertJsonValue(snapshot, Object.values(fields).some(field => field.type === 'array') ? { maxBytes: ARRAY_LIMITS.messageBytes, maxEntries: 4096 } : undefined);
|
|
23
|
+
assertFormStateSelection(selection, fields);
|
|
24
|
+
const keys = ['formId', 'revision', ...(selection.values !== undefined ? ['values'] : []), ...(selection.errors !== undefined ? ['errors'] : []), ...['submitting', 'validating', 'formError'].filter(key => selection[key]), ...(selection.arrayErrors !== undefined ? ['arrayErrors'] : []), ...(selection.validatingFields !== undefined ? ['validatingFields'] : [])];
|
|
25
|
+
only(snapshot, keys);
|
|
26
|
+
if (Object.keys(snapshot).length !== keys.length || snapshot.formId !== formId || !Number.isSafeInteger(snapshot.revision) || snapshot.revision < 1) fail();
|
|
27
|
+
if (selection.values !== undefined) {
|
|
28
|
+
const names = selectedNames(selection.values, fields);
|
|
29
|
+
only(snapshot.values, names);
|
|
30
|
+
if (Object.keys(snapshot.values).length !== names.length) fail();
|
|
31
|
+
for (const name of names) assertFieldValue(snapshot.values[name], fields[name]);
|
|
32
|
+
}
|
|
33
|
+
if (selection.errors !== undefined) {
|
|
34
|
+
only(snapshot.errors, selectedNames(selection.errors, fields));
|
|
35
|
+
assertErrorTree(snapshot.errors, fields, snapshot.values);
|
|
36
|
+
}
|
|
37
|
+
for (const key of ['submitting', 'validating']) if (selection[key] && typeof snapshot[key] !== 'boolean') fail();
|
|
38
|
+
if (selection.formError && snapshot.formError !== null && (typeof snapshot.formError !== 'string' || !snapshot.formError || snapshot.formError.length > 4096)) fail();
|
|
39
|
+
if (selection.validatingFields !== undefined) {
|
|
40
|
+
only(snapshot.validatingFields, selectedNames(selection.validatingFields, fields));
|
|
41
|
+
assertErrorTree(snapshot.validatingFields, fields, snapshot.values, true);
|
|
42
|
+
}
|
|
43
|
+
if (selection.arrayErrors !== undefined) { only(snapshot.arrayErrors, selectedNames(selection.arrayErrors, fields)); assertArrayErrors(snapshot.arrayErrors, fields); }
|
|
44
|
+
if (Object.values(fields).some(field => field.type === 'array') && formBytes(snapshot) > ARRAY_LIMITS.messageBytes) fail();
|
|
45
|
+
return snapshot;
|
|
46
|
+
}
|
|
47
|
+
export function freezeFormState(value) {
|
|
48
|
+
if (value && typeof value === 'object') { Object.values(value).forEach(freezeFormState); Object.freeze(value); }
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
export function initialFormState(formId, fields, initialValues, selection) {
|
|
52
|
+
const snapshot = { formId, revision: 1 };
|
|
53
|
+
if (selection.values !== undefined) { const values = normalizeFormValues(fields, initialValues); snapshot.values = Object.fromEntries(selectedNames(selection.values, fields).map(name => [name, values[name]])); }
|
|
54
|
+
if (selection.errors !== undefined) snapshot.errors = {};
|
|
55
|
+
if (selection.arrayErrors !== undefined) snapshot.arrayErrors = {};
|
|
56
|
+
for (const key of ['submitting', 'validating']) if (selection[key]) snapshot[key] = false;
|
|
57
|
+
if (selection.formError) snapshot.formError = null;
|
|
58
|
+
if (selection.validatingFields !== undefined) snapshot.validatingFields = {};
|
|
59
|
+
assertFormStateSnapshot(snapshot, formId, fields, selection);
|
|
60
|
+
return snapshot;
|
|
61
|
+
}
|
|
62
|
+
export function createWithState({ createElement, Fragment, useMemo, useState, useRef }, formId, schema, initialValues) {
|
|
63
|
+
function Observer({ selection, children }) {
|
|
64
|
+
const revision = useRef(0);
|
|
65
|
+
const [snapshot, setSnapshot] = useState(() => initialFormState(formId, schema.fields, initialValues, selection));
|
|
66
|
+
const onState = useMemo(() => next => {
|
|
67
|
+
assertFormStateSnapshot(next, formId, schema.fields, selection);
|
|
68
|
+
if (next.revision <= revision.current) return;
|
|
69
|
+
revision.current = next.revision;
|
|
70
|
+
setSnapshot(freezeFormState(next));
|
|
71
|
+
}, []);
|
|
72
|
+
const { formId: _, revision: _revision, ...state } = snapshot;
|
|
73
|
+
return createElement(Fragment, null,
|
|
74
|
+
createElement('FormStateObserver', { formId, selection, onState }),
|
|
75
|
+
children(freezeFormState(state)));
|
|
76
|
+
}
|
|
77
|
+
return function WithState({ selection, children }) {
|
|
78
|
+
assertFormStateSelection(selection, schema.fields);
|
|
79
|
+
if (typeof children !== 'function') fail();
|
|
80
|
+
return createElement(Observer, { key: JSON.stringify(selection), selection, children });
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const VALIDATION_TIMEOUT_MS: 10000;
|
|
2
|
+
export interface ValidationMetadata { fields: string[]; form: boolean; mode: 'onBlur' | 'onChange' | 'onSubmit'; }
|
|
3
|
+
export interface ValidationRequest {
|
|
4
|
+
formId: string; runId: number; reason: 'blur' | 'change' | 'submit';
|
|
5
|
+
values: Record<string, unknown>; fields: string[]; includeForm: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ValidationResponse { formId: string; runId: number; fieldErrors: Record<string, string | (Record<string, string> | null)[]>; arrayErrors?: Record<string, string>; formError?: string; }
|
|
8
|
+
export function assertValidationMetadata(value: unknown, fields: Record<string, unknown>): ValidationMetadata;
|
|
9
|
+
export function assertValidationResponse(value: unknown, request: Pick<ValidationRequest, 'formId' | 'runId'> & { values?: Record<string, unknown> }, fields: Record<string, unknown>): ValidationResponse;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { assertFieldValue, assertArrayCapacity, assertErrorTree, assertArrayErrors, fieldAtPath, valueAtPath, rulePath, flattenErrors, nestErrors, formBytes, ARRAY_LIMITS } from './array-contract.js';
|
|
2
|
+
import { freezeFormState } from './state.js';
|
|
3
|
+
import { isDateTime, isPlainDate } from './values.js';
|
|
4
|
+
import { assertRecordRef, recordObjectsForField } from './record-options.js';
|
|
5
|
+
|
|
6
|
+
export const VALIDATION_TIMEOUT_MS = 10000;
|
|
7
|
+
const fail = () => { throw new Error('runtime_form_validation_invalid'); };
|
|
8
|
+
const only = (value, keys) => {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(key => !keys.includes(key))) fail();
|
|
10
|
+
};
|
|
11
|
+
const validMessage = value => typeof value === 'string' && value.trim() && value.length <= 300;
|
|
12
|
+
export function assertValidationMetadata(value, fields) {
|
|
13
|
+
only(value, ['fields', 'form', 'mode']);
|
|
14
|
+
if (!Array.isArray(value.fields) || value.fields.length > 69 || new Set(value.fields).size !== value.fields.length
|
|
15
|
+
|| value.fields.some(name => { try { fieldAtPath(fields, typeof name === 'string' ? name.replace('[*]', '[0]') : name); return false; } catch { return true; } })
|
|
16
|
+
|| typeof value.form !== 'boolean' || !['onBlur', 'onChange', 'onSubmit'].includes(value.mode)) fail();
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
export function assertValidationResponse(value, request, fields) {
|
|
20
|
+
only(value, ['formId', 'runId', 'fieldErrors', 'arrayErrors', 'formError']);
|
|
21
|
+
if (value.formId !== request.formId || value.runId !== request.runId) fail();
|
|
22
|
+
assertErrorTree(value.fieldErrors, fields, request.values, false, 300);
|
|
23
|
+
if (value.arrayErrors !== undefined) assertArrayErrors(value.arrayErrors, fields, 300);
|
|
24
|
+
if (value.formError !== undefined && !validMessage(value.formError)) fail();
|
|
25
|
+
if (Object.values(fields).some(field => field.type === 'array') && formBytes({ ...value, values: request.values }) > ARRAY_LIMITS.messageBytes - 32768) fail();
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function assertRequest(request, formId, fields, validators, validate) {
|
|
29
|
+
only(request, ['formId', 'runId', 'reason', 'values', 'fields', 'includeForm']);
|
|
30
|
+
if (request.formId !== formId || !Number.isSafeInteger(request.runId) || request.runId < 1
|
|
31
|
+
|| !['blur', 'change', 'submit'].includes(request.reason) || typeof request.includeForm !== 'boolean'
|
|
32
|
+
|| request.includeForm && !validate || !Array.isArray(request.fields) || new Set(request.fields).size !== request.fields.length
|
|
33
|
+
|| request.fields.length > 303 || request.fields.some(name => !Object.hasOwn(validators, rulePath(name)))) fail();
|
|
34
|
+
only(request.values, Object.keys(fields));
|
|
35
|
+
if (Object.keys(request.values).length !== Object.keys(fields).length) fail();
|
|
36
|
+
for (const [name, field] of Object.entries(fields)) assertFieldValue(request.values[name], field);
|
|
37
|
+
assertArrayCapacity(fields, request.values);
|
|
38
|
+
for (const path of request.fields) if (valueAtPath(request.values, path) === undefined) fail();
|
|
39
|
+
}
|
|
40
|
+
export function createValidationCallback(formId, fields, validators, validate) {
|
|
41
|
+
let busy = false;
|
|
42
|
+
return async input => {
|
|
43
|
+
assertRequest(input, formId, fields, validators, validate);
|
|
44
|
+
if (busy) fail();
|
|
45
|
+
busy = true;
|
|
46
|
+
const request = freezeFormState(structuredClone(input));
|
|
47
|
+
let active = true, timer, position = 0;
|
|
48
|
+
const fieldErrors = {}, arrayErrors = {};
|
|
49
|
+
const runField = async () => {
|
|
50
|
+
while (active && position < request.fields.length) {
|
|
51
|
+
const name = request.fields[position++], value = valueAtPath(request.values, name), field = fieldAtPath(fields, name);
|
|
52
|
+
if (!field.required && (value === '' || value === null)) continue;
|
|
53
|
+
for (const validator of validators[rulePath(name)]) {
|
|
54
|
+
if (!active) return;
|
|
55
|
+
const error = await validator(value);
|
|
56
|
+
if (!active) return;
|
|
57
|
+
if (error !== undefined) { if (!validMessage(error)) fail(); (field.type === 'array' ? arrayErrors : fieldErrors)[name] = error; break; }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
try {
|
|
62
|
+
return await Promise.race([
|
|
63
|
+
(async () => {
|
|
64
|
+
await Promise.all([runField(), runField()]);
|
|
65
|
+
if (!active) return;
|
|
66
|
+
const result = request.includeForm ? await validate(request.values) : undefined;
|
|
67
|
+
if (!active) return;
|
|
68
|
+
if (result !== undefined) { only(result, ['fieldErrors', 'arrayErrors', 'formError']); }
|
|
69
|
+
if (result?.fieldErrors !== undefined) assertErrorTree(result.fieldErrors, fields, request.values, false, 300);
|
|
70
|
+
if (result?.arrayErrors !== undefined) assertArrayErrors(result.arrayErrors, fields, 300);
|
|
71
|
+
// 每个叶子合并,不能用整行覆盖同一行其他字段的跨字段错误。
|
|
72
|
+
const arrays = { ...result?.arrayErrors, ...arrayErrors };
|
|
73
|
+
const response = { formId, runId: request.runId, fieldErrors: nestErrors({ ...flattenErrors(result?.fieldErrors), ...fieldErrors }),
|
|
74
|
+
...(Object.keys(arrays).length ? { arrayErrors: arrays } : {}), ...(result?.formError === undefined ? {} : { formError: result.formError }) };
|
|
75
|
+
return assertValidationResponse(response, request, fields);
|
|
76
|
+
})(),
|
|
77
|
+
new Promise((_, reject) => { timer = setTimeout(() => { active = false; reject(new Error('runtime_form_validation_timeout')); }, VALIDATION_TIMEOUT_MS); }),
|
|
78
|
+
]);
|
|
79
|
+
} finally { active = false; busy = false; clearTimeout(timer); }
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function isPlainDate(value: unknown): value is string;
|
|
2
|
+
export function isDateTime(value: unknown): value is string;
|
|
3
|
+
export function parseNumberDraft(value: unknown): { value: number | null; error?: string };
|
|
4
|
+
export function instantToLocal(value: string | null): string;
|
|
5
|
+
export function localToInstant(value: string): { value: string | null; error?: string };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** 平台值格式,不包含业务字段规则或缺省时区。 */
|
|
2
|
+
export function isPlainDate(value) {
|
|
3
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
4
|
+
const [year, month, day] = value.split('-').map(Number);
|
|
5
|
+
const date = new Date(0);
|
|
6
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
7
|
+
return year > 0 && date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
8
|
+
}
|
|
9
|
+
export function isDateTime(value) {
|
|
10
|
+
if (typeof value !== 'string') return false;
|
|
11
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?(Z|[+-]\d{2}:\d{2})$/.exec(value);
|
|
12
|
+
return Boolean(match && isPlainDate(match[1]) && +match[2] < 24 && +match[3] < 60 && +(match[4] ?? 0) < 60 && Number.isFinite(Date.parse(value)));
|
|
13
|
+
}
|
|
14
|
+
export function parseNumberDraft(value) {
|
|
15
|
+
if (value === null || value === '') return { value: null };
|
|
16
|
+
if (typeof value === 'number') return Number.isFinite(value) ? { value } : { value: null, error: '请输入有效数字' };
|
|
17
|
+
if (typeof value !== 'string' || value.length > 512 || !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) return { value: null, error: '请输入有效数字' };
|
|
18
|
+
const number = Number(value);
|
|
19
|
+
return Number.isFinite(number) ? { value: number } : { value: null, error: '请输入有效数字' };
|
|
20
|
+
}
|
|
21
|
+
const pad = value => String(value).padStart(2, '0');
|
|
22
|
+
export function instantToLocal(value) {
|
|
23
|
+
if (!value) return '';
|
|
24
|
+
if (!isDateTime(value)) return value;
|
|
25
|
+
const date = new Date(value);
|
|
26
|
+
return `${String(date.getFullYear()).padStart(4, '0')}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
27
|
+
}
|
|
28
|
+
export function localToInstant(value) {
|
|
29
|
+
if (!value) return { value: null };
|
|
30
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})$/.exec(value);
|
|
31
|
+
if (!match || !isPlainDate(match[1]) || +match[2] > 23 || +match[3] > 59) return { value: null, error: '请输入有效日期时间' };
|
|
32
|
+
const wall = new Date(value);
|
|
33
|
+
const utc = new Date(value + ':00.000Z').getTime();
|
|
34
|
+
const offsets = new Set([-36, 0, 36].map(hours => new Date(wall.getTime() + hours * 3600000).getTimezoneOffset()));
|
|
35
|
+
const candidates = [...offsets].map(offset => new Date(utc + offset * 60000).toISOString()).filter(instant => instantToLocal(instant) === value);
|
|
36
|
+
if (candidates.length !== 1) return { value: null, error: '该本地时间不存在或有歧义,请选择其他时间' };
|
|
37
|
+
return { value: candidates[0] };
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** 静态信息进入不可变 Manifest;工具集合由同目录 .tools.ts 唯一决定。 */
|
|
2
|
+
export interface AppAgentDefinition {
|
|
3
|
+
key: string;
|
|
4
|
+
name: string;
|
|
5
|
+
/** App 提供的系统提示词,最多 16KiB UTF-8,不交付 Client Worker。 */
|
|
6
|
+
systemPrompt: string;
|
|
7
|
+
/** 引用同 App 独立 Action 的 key,Schema/execute 只维护一份。 */
|
|
8
|
+
actionRefs?: readonly string[];
|
|
9
|
+
aiExposure?: AppExpertExposure;
|
|
10
|
+
}
|
|
11
|
+
export type ToolSchema = Readonly<Record<string, unknown>>;
|
|
12
|
+
export interface AppToolDefinition<Input, Output> {
|
|
13
|
+
key: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
/** 有界 JSON Schema;平台在模型调用和结果交付时再次严格验证。 */
|
|
17
|
+
inputSchema: ToolSchema;
|
|
18
|
+
outputSchema: ToolSchema;
|
|
19
|
+
/** 默认 none;output 只允许成功且通过校验的结果进入实时 callback。 */
|
|
20
|
+
clientResult?: 'none' | 'output';
|
|
21
|
+
/** 缺省私有;公开查询声明不代替执行授权。 */
|
|
22
|
+
aiExposure?: AppActionExposure;
|
|
23
|
+
/** 同 App 静态 Tool Renderer;展示不授予服务端执行或业务写入权限。 */
|
|
24
|
+
toolRenderer?: { key: string; showByDefault: boolean;
|
|
25
|
+
/** 成功结果中的标量身份路径(最多四段);同轮同安装/Action/Renderer/身份仅展示一次。仅控制展示,不跳过取数或授权。 */
|
|
26
|
+
dedupeBy?: string;
|
|
27
|
+
};
|
|
28
|
+
/** 仅在所属安装的服务端执行环境运行,凭据沿用现有 Server SDK。 */
|
|
29
|
+
execute(input: Input): Output | Promise<Output>;
|
|
30
|
+
}
|
|
31
|
+
export function defineAgent(definition: AppAgentDefinition): Readonly<AppAgentDefinition>;
|
|
32
|
+
export function defineTool<Input = Record<string, unknown>, Output = unknown>(definition: AppToolDefinition<Input, Output>): Readonly<AppToolDefinition<Input, Output>>;
|
|
33
|
+
|
|
34
|
+
export interface AppActionExposure {
|
|
35
|
+
effect: 'query';
|
|
36
|
+
/** 公开 object 标识;只用于说明适用对象,不授予权限。 */
|
|
37
|
+
subjectTypes: readonly string[];
|
|
38
|
+
usage: string;
|
|
39
|
+
limitations: string;
|
|
40
|
+
/** 已有 inputSchema 中接收 {object, recordId} 的字段;由 Host 绑定。 */
|
|
41
|
+
subjectInput?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface AppExpertExposure {
|
|
44
|
+
subjectTypes: readonly string[];
|
|
45
|
+
usage: string;
|
|
46
|
+
limitations: string;
|
|
47
|
+
outputSchema: ToolSchema;
|
|
48
|
+
/** 只允许显式开放的原私有工具子集;独立能力沿 actionRefs。 */
|
|
49
|
+
toolKeys?: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
interface AppDataCapabilityBase {
|
|
52
|
+
key: string;
|
|
53
|
+
name: string;
|
|
54
|
+
description: string;
|
|
55
|
+
subjectTypes: readonly string[];
|
|
56
|
+
usage: string;
|
|
57
|
+
}
|
|
58
|
+
export type AppDataCapabilityDefinition = AppDataCapabilityBase & (
|
|
59
|
+
{ kind: 'live'; actionRef: string; sourceBinding?: never } |
|
|
60
|
+
{ kind: 'published'; sourceBinding: string; actionRef?: never }
|
|
61
|
+
);
|
|
62
|
+
export function defineDataCapability<T extends AppDataCapabilityDefinition>(definition: T): Readonly<T>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { assertDataCapability } from '../../toolchain/ai-capabilities.js';
|
|
2
|
+
import { assertAiExposure } from '../../toolchain/agents/exposure.js';
|
|
3
|
+
import { assertToolRendererReference } from '../../toolchain/agents/manifest.js';
|
|
4
|
+
const KEY = /^[a-z][a-z0-9-]{0,63}$/;
|
|
5
|
+
|
|
6
|
+
function fields(value, required, optional = []) {
|
|
7
|
+
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
|
8
|
+
required.some(key => !Object.hasOwn(value, key)) ||
|
|
9
|
+
Object.keys(value).some(key => ![...required, ...optional].includes(key))) {
|
|
10
|
+
throw new TypeError("agent_definition_invalid");
|
|
11
|
+
}
|
|
12
|
+
if (typeof value.key !== "string" || value.key.trim() !== value.key || !KEY.test(value.key) || typeof value.name !== "string" ||
|
|
13
|
+
!value.name.trim() || value.name.length > 80) throw new TypeError("agent_definition_invalid");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function defineAgent(definition) {
|
|
17
|
+
fields(definition, ["key", "name", "systemPrompt"], ["actionRefs", "aiExposure"]);
|
|
18
|
+
if (typeof definition.systemPrompt !== "string" || !definition.systemPrompt.trim() ||
|
|
19
|
+
new TextEncoder().encode(definition.systemPrompt).length > 16384) {
|
|
20
|
+
throw new TypeError("agent_definition_invalid");
|
|
21
|
+
}
|
|
22
|
+
return Object.freeze({ ...definition });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function defineTool(definition) {
|
|
26
|
+
fields(definition, ["key", "name", "description", "inputSchema", "outputSchema", "execute"], ["clientResult", "aiExposure", "toolRenderer"]);
|
|
27
|
+
if (typeof definition.execute !== "function" || typeof definition.description !== "string" ||
|
|
28
|
+
!definition.description.trim() || definition.description.length > 1200 ||
|
|
29
|
+
![undefined, "none", "output"].includes(definition.clientResult)) {
|
|
30
|
+
throw new TypeError("agent_tool_definition_invalid");
|
|
31
|
+
}
|
|
32
|
+
if (definition.aiExposure !== undefined) assertAiExposure(definition.aiExposure, definition.inputSchema);
|
|
33
|
+
if (definition.toolRenderer !== undefined) assertToolRendererReference(definition.toolRenderer);
|
|
34
|
+
return Object.freeze({ ...definition, clientResult: definition.clientResult ?? "none" });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function defineDataCapability(definition) {
|
|
38
|
+
assertDataCapability(definition);
|
|
39
|
+
return Object.freeze({ ...definition });
|
|
40
|
+
}
|
package/src/server/index.d.ts
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
|
+
export { defineAgent, defineTool, defineDataCapability, type AppAgentDefinition, type AppToolDefinition, type ToolSchema } from "./agents/index.js";
|
|
1
2
|
import type { JsonValue } from "../index.js";
|
|
3
|
+
export { defineWorkflowStep, defineWorkflowStepExecute } from "./steps/index.js";
|
|
4
|
+
export type { WorkflowObjectSchema, WorkflowScalarSchema, WorkflowStepDefinition, WorkflowStepOutcome, WorkflowStepResult, WorkflowSchemaValue } from "./steps/index.js";
|
|
5
|
+
export { defineWorkflowTrigger, defineWorkflowTriggerActivate, defineWorkflowTriggerOnEvent, defineWorkflowTriggerDeactivate } from "./triggers/index.js";
|
|
6
|
+
export type { WorkflowTriggerDefinition, WorkflowTriggerOutcome, WorkflowTriggerEventResult } from "./triggers/index.js";
|
|
7
|
+
export {
|
|
8
|
+
defineWorkflowBlock,
|
|
9
|
+
defineWorkflowBlockExecute,
|
|
10
|
+
defineWorkflowBlockActivate,
|
|
11
|
+
defineWorkflowBlockTrigger,
|
|
12
|
+
defineWorkflowBlockDeactivate,
|
|
13
|
+
} from "./workflow-blocks/index.js";
|
|
14
|
+
export type {
|
|
15
|
+
WorkflowBlockDefinition,
|
|
16
|
+
WorkflowStepBlockDefinition,
|
|
17
|
+
WorkflowTriggerBlockDefinition,
|
|
18
|
+
} from "./workflow-blocks/index.js";
|
|
2
19
|
|
|
3
20
|
/** 当前执行的 App API Token;由可信 Runtime Module 在服务端模块求值前注入。 */
|
|
4
21
|
export const ZHIKE_API_TOKEN: string;
|
|
22
|
+
/** 认证页面 Function 的当前成员短期 OpenAPI Token;未申请 User scopes 或后台入口为 undefined。 */
|
|
23
|
+
export const ZHIKE_CURRENT_USER_TOKEN: string | undefined;
|
|
5
24
|
|
|
6
25
|
/** 由调用方选择稳定操作身份;SDK 不生成 Key 或自动重试。 */
|
|
7
26
|
export interface IdempotencyOptions { idempotencyKey?: string }
|
|
@@ -206,21 +225,55 @@ export interface WorkspaceWebhookBody {
|
|
|
206
225
|
events: WorkspaceWebhookEvent[];
|
|
207
226
|
}
|
|
208
227
|
|
|
209
|
-
|
|
210
|
-
export interface WorkspaceConnection {
|
|
228
|
+
interface WorkspaceConnectionBase {
|
|
211
229
|
/** 当前 Workspace Connection 的稳定 ID。 */
|
|
212
230
|
id: string;
|
|
213
231
|
/** 当前 Connect/Reconnect generation 的非敏感稳定表示。 */
|
|
214
232
|
generation: string;
|
|
215
|
-
/**
|
|
216
|
-
type: "secret" | "oauth2";
|
|
217
|
-
/** Secret 或当前 OAuth access token,仅存在于本次服务端执行内存。 */
|
|
233
|
+
/** Secret 或当前 OAuth/OIDC access token,仅存在于本次服务端执行内存。 */
|
|
218
234
|
value: string;
|
|
219
235
|
}
|
|
220
236
|
|
|
237
|
+
/** 当前服务端执行可使用的 Workspace 外部系统连接。 */
|
|
238
|
+
export type WorkspaceConnection = WorkspaceConnectionBase & (
|
|
239
|
+
| {
|
|
240
|
+
type: "secret";
|
|
241
|
+
protocol: "secret";
|
|
242
|
+
}
|
|
243
|
+
| {
|
|
244
|
+
type: "oauth2";
|
|
245
|
+
protocol: "oauth2";
|
|
246
|
+
/** OAuth 2.0 access token;等同 value。 */
|
|
247
|
+
accessToken: string;
|
|
248
|
+
}
|
|
249
|
+
| {
|
|
250
|
+
type: "oauth2";
|
|
251
|
+
protocol: "oidc";
|
|
252
|
+
/** OIDC access token;等同 value。 */
|
|
253
|
+
accessToken: string;
|
|
254
|
+
/** OIDC ID token;仅存在于当前服务端执行内存。 */
|
|
255
|
+
idToken: string;
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
|
|
221
259
|
/** 同步读取当前执行的连接快照;未连接或暂不可用时抛出安全错误,不在 getter 内刷新。 */
|
|
222
260
|
export function getWorkspaceConnection(): Readonly<WorkspaceConnection>;
|
|
223
261
|
|
|
262
|
+
/** 当前 Server Function 调用者在当前 Workspace 内的成员快照。 */
|
|
263
|
+
export interface CurrentUser {
|
|
264
|
+
/** Workspace 成员 ID;不是平台全局 User ID。 */
|
|
265
|
+
workspaceMemberId: string;
|
|
266
|
+
/** 当前 Workspace 中的显示名称。 */
|
|
267
|
+
displayName: string;
|
|
268
|
+
/** 当前 Workspace 中的主邮箱。 */
|
|
269
|
+
primaryEmailAddress: string | null;
|
|
270
|
+
/** 当前平台用户头像;未绑定平台用户时为空。 */
|
|
271
|
+
avatarUrl: string | null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** 同步读取当前调用者;Lifecycle、Webhook 等无人触发入口会抛出 current_user_unavailable。 */
|
|
275
|
+
export function getCurrentUser(): Readonly<CurrentUser>;
|
|
276
|
+
|
|
224
277
|
/** Server Function 的一期函数合同。 */
|
|
225
278
|
export type ServerFunction<TArgs extends JsonValue[] = JsonValue[], TResult extends JsonValue = JsonValue> =
|
|
226
279
|
(...args: TArgs) => Promise<TResult>;
|
|
@@ -266,3 +319,5 @@ export interface WebhookContext {
|
|
|
266
319
|
/** 从 1 开始的业务尝试序号。 */
|
|
267
320
|
readonly attemptNumber: number;
|
|
268
321
|
}
|
|
322
|
+
|
|
323
|
+
export type { AppActionExposure, AppExpertExposure, AppDataCapabilityDefinition } from "./agents/index.js";
|