blob: c28a90d79a2ab6c3c4db9b3f25de6f95bbf305f0 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { z } from 'zod';
import { defineOperation } from './operation-spec.js';
import { requireEncodedByteLimit } from './codec.js';
/** Persisted avatar metadata. Image bytes travel separately, never in model context. */
export const assistantEmojiSchema = z
.string()
.trim()
.min(1)
.max(32)
.refine(
(value) =>
[...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(value)].length === 1 &&
/[\p{Extended_Pictographic}\p{Regional_Indicator}]/u.test(value),
'Use one emoji for the avatar',
);
export const assistantAvatarSchema = z
.object({
emoji: assistantEmojiSchema.optional(),
image: z
.object({
id: z.string().regex(/^[a-f0-9]{64}$/),
mimeType: z.enum(['image/png', 'image/jpeg', 'image/webp', 'image/gif']),
length: z
.number()
.int()
.positive()
.max(3 * 1024 * 1024),
})
.strict()
.optional(),
background: z
.string()
.regex(/^#[0-9a-fA-F]{6}$/)
.default('#E5EDFF'),
})
.strict()
.refine((value) => Boolean(value.emoji) !== Boolean(value.image), 'Provide an emoji or image');
export type AssistantAvatar = z.infer<typeof assistantAvatarSchema>;
export const DEFAULT_ASSISTANT_AVATAR: AssistantAvatar = { emoji: '🤖', background: '#E5EDFF' };
const request = z
.object({
method: z.enum([
'assistant.enablement',
'assistant.set-enabled',
'assistant.binding',
'assistant.allocate-workspace',
'assistant.card-status',
'assistant.card-reply',
'assistant.avatar',
'assistant.character-status',
'assistant.avatar-image',
'assistant.original',
'assistant.status',
'assistant.memory-activity',
'assistant.bind',
'assistant.control',
'assistant.quick-discovery',
'assistant.quick-discovery-status',
'assistant.recover',
'assistant.adopt-task',
]),
input: z.unknown(),
})
.strict();
export type PersonalAssistantRequest = z.infer<typeof request>;
export const PERSONAL_ASSISTANT_OPERATION_SPECS = {
'personal-assistant.call': defineOperation<
PersonalAssistantRequest,
{ value: unknown },
| 'host_not_ready'
| 'host_draining'
| 'operation_unavailable'
| 'invalid_request'
| 'internal_failure'
>({
mode: 'command',
availability: 'ready',
errors: [
'host_not_ready',
'host_draining',
'operation_unavailable',
'invalid_request',
'internal_failure',
],
decodeInput(value) {
requireEncodedByteLimit(value, 'Personal assistant request', 32 * 1024);
return request.parse(value);
},
decodeOutput(value) {
requireEncodedByteLimit(value, 'Personal assistant response', 480 * 1024);
return z.object({ value: z.unknown() }).strict().parse(value);
},
}),
} as const;