ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
request.c File Reference

Go to the source code of this file.

Macros

#define MCP_SERVER_REQUEST_BUF_BUFFER_SIZE   4097
#define MCP_SERVER_REQUEST_INDEX_BUFFER_SIZE   16
#define MCP_SERVER_REQUEST_VALUE_BUFFER_SIZE   64
#define MCP_SERVER_REQUEST_B_BUFFER_SIZE   16
#define MCP_REQUEST_ARGV_CAPACITY   256
#define MCP_REQUEST_RESERVED_ARGS   6

Functions

yyjson_mut_val * mcp_tools_schema (yyjson_mut_doc *d, const mcp_policy_t *p)
asciichat_error_t mcp_request_parse (const mcp_policy_t *p, mcp_tool_t tool, yyjson_val *a, http_renderer_process_request_t *r)
void mcp_request_destroy (http_renderer_process_request_t *r)

Macro Definition Documentation

◆ MCP_REQUEST_ARGV_CAPACITY

#define MCP_REQUEST_ARGV_CAPACITY   256

Definition at line 9 of file request.c.

Referenced by mcp_request_parse().

◆ MCP_REQUEST_RESERVED_ARGS

#define MCP_REQUEST_RESERVED_ARGS   6

Definition at line 10 of file request.c.

◆ MCP_SERVER_REQUEST_B_BUFFER_SIZE

#define MCP_SERVER_REQUEST_B_BUFFER_SIZE   16

Definition at line 6 of file request.c.

Referenced by mcp_request_parse().

◆ MCP_SERVER_REQUEST_BUF_BUFFER_SIZE

#define MCP_SERVER_REQUEST_BUF_BUFFER_SIZE   4097

Definition at line 3 of file request.c.

Referenced by mcp_request_parse().

◆ MCP_SERVER_REQUEST_INDEX_BUFFER_SIZE

#define MCP_SERVER_REQUEST_INDEX_BUFFER_SIZE   16

Definition at line 4 of file request.c.

Referenced by mcp_request_parse().

◆ MCP_SERVER_REQUEST_VALUE_BUFFER_SIZE

#define MCP_SERVER_REQUEST_VALUE_BUFFER_SIZE   64

Definition at line 5 of file request.c.

Referenced by mcp_request_parse().

Function Documentation

◆ mcp_request_destroy()

void mcp_request_destroy ( http_renderer_process_request_t * r)

Definition at line 678 of file request.c.

678 {
679 for (size_t i = 0; i < r->argc; ++i)
680 SAFE_FREE(r->argv[i]);
681 SAFE_FREE(r->argv);
682 if (r->upload_dir[0]) {
683 asciichat_error_t e = platform_rmdir_recursive(r->upload_dir);
684 (void)e;
685 }
686 if (r->stage_dir[0]) {
687 asciichat_error_t e = platform_rmdir_recursive(r->stage_dir);
688 (void)e;
689 }
690 memset(r, 0, sizeof(*r));
691}
#define SAFE_FREE(ptr)
Definition common.h:377
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
char upload_dir[PLATFORM_MAX_PATH_LENGTH]
char stage_dir[PLATFORM_MAX_PATH_LENGTH]

References http_renderer_process_request_t::argc, http_renderer_process_request_t::argv, SAFE_FREE, http_renderer_process_request_t::stage_dir, and http_renderer_process_request_t::upload_dir.

Referenced by mcp_dispatch().

◆ mcp_request_parse()

asciichat_error_t mcp_request_parse ( const mcp_policy_t * p,
mcp_tool_t tool,
yyjson_val * a,
http_renderer_process_request_t * r )

Definition at line 416 of file request.c.

417 {
418 memset(r, 0, sizeof(*r));
420 r->options.width = 100;
421 r->options.height = 40;
422 r->options.color_mode = TERM_COLOR_NONE;
423 r->timeout_ms = 45000;
424 r->argv = SAFE_CALLOC(MCP_REQUEST_ARGV_CAPACITY, sizeof(char *), char **);
425 static const char *const fields[] = {"path", "data_base64", "mime_type", "url", "video_device",
426 "audio", "visualization", "options", "output_path", "timeout_ms",
427 "audio_device", "volume", "output_format", "output", NULL};
428 asciichat_error_t err = check_fields(a, fields);
429 if (err)
430 return err;
431 const char *source_fields[] = {"path", "data_base64", "mime_type", "url", "video_device",
432 "audio", "visualization", "audio_device", "volume"};
433 for (size_t i = 0; i < 9; ++i) {
434 bool allowed = tool == MCP_FILE ? i < 3 : tool == MCP_URL ? i == 3 : tool == MCP_WEBCAM ? i == 4 || i >= 7 : i >= 5;
435 if (!allowed && mcp_get(a, source_fields[i]))
436 return SET_ERRNO(ERROR_INVALID_PARAM, "Argument %s is not valid for this tool", source_fields[i]);
437 }
438 if (p->restricted && (tool == MCP_WEBCAM || mcp_get(a, "path") || mcp_get(a, "output_path") ||
439 mcp_get(a, "audio_device") || mcp_get(a, "volume")))
440 return SET_ERRNO(ERROR_INVALID_PARAM, "Local capability is unavailable in server mode");
441 yyjson_val *timeout = mcp_get(a, "timeout_ms");
442 if (timeout) {
443 if (!yyjson_is_int(timeout) || yyjson_get_sint(timeout) < 1000 || yyjson_get_sint(timeout) > 60000)
444 return SET_ERRNO(ERROR_INVALID_PARAM, "timeout_ms must be 1000–60000");
445 r->timeout_ms = (unsigned)yyjson_get_uint(timeout);
446 }
447 char exe[PLATFORM_MAX_PATH_LENGTH];
448 if (!platform_get_executable_path(exe, sizeof(exe)))
449 return SET_ERRNO(ERROR_FILE_OPERATION, "Cannot locate renderer executable");
450 yyjson_val *opts = mcp_get(a, "options");
451 if (opts && !yyjson_is_obj(opts))
452 return SET_ERRNO(ERROR_INVALID_PARAM, "options must be an object");
453 yyjson_val *format_value = mcp_get(a, "output_format"), *output_value = mcp_get(a, "output_path"),
454 *output_mode = mcp_get(a, "output");
455 if (output_mode && (!yyjson_is_str(output_mode) ||
456 (strcmp(yyjson_get_str(output_mode), "text") && strcmp(yyjson_get_str(output_mode), "files"))))
457 return SET_ERRNO(ERROR_INVALID_PARAM, "output must be text or files");
458 if (output_mode && !strcmp(yyjson_get_str(output_mode), "text") && output_value)
459 return SET_ERRNO(ERROR_INVALID_PARAM, "output=text cannot be combined with output_path");
460 if (output_mode && !strcmp(yyjson_get_str(output_mode), "files"))
462 const char *output_name = yyjson_is_str(output_value) ? yyjson_get_str(output_value) : "";
463 const char *output_ext = strrchr(output_name, '.');
464 bool gif_requested = yyjson_is_str(format_value) && !strcmp(yyjson_get_str(format_value), "gif");
465 bool gif_path = output_ext && !strcmp(output_ext, ".gif");
466 bool gif_output = gif_requested || gif_path;
467 bool media_tool = tool == MCP_URL || tool == MCP_FILE;
468 bool media_source = mcp_get(a, "url") || mcp_get(a, "path") || mcp_get(a, "data_base64");
469 bool snapshot_requested = mcp_get(opts, "snapshot") || mcp_get(opts, "snapshot-delay");
470 bool gif_auto = gif_output && media_tool && media_source && !snapshot_requested;
471 arg(r, exe);
472 arg(r, "--log-level=error");
473 arg(r, "--no-check-update");
474 arg(r, "mirror");
475 option(r, "width", "100");
476 option(r, "height", "40");
477 option(r, "color-mode", "none");
478 option(r, "strip-ansi", "true");
479 option(r, "snapshot", gif_auto ? "false" : "true");
480 if (!gif_auto && !yyjson_is_false(mcp_get(opts, "snapshot")))
481 option(r, "snapshot-delay", "0");
482 option(r, "splash-screen", "false");
483 option(r, "no-audio-playback", "true");
484 option(r, "audio", "false");
485 size_t i, max;
486 yyjson_val *k, *v;
487 if (opts)
488 yyjson_obj_foreach(opts, i, max, k, v) {
489 const char *name = yyjson_get_str(k);
490 const registry_entry_t *e = exposed(p, name);
491 if (strlen(name) != yyjson_get_len(k) || !e)
492 return SET_ERRNO(ERROR_INVALID_PARAM, "Unsupported option: %s", name);
494 bool omitted = false;
495 err = options_json_parse_value(name, v, buf, sizeof(buf), &omitted);
496 if (err)
497 return err;
498 if (omitted)
499 continue;
500 double n = yyjson_get_num(v);
501 if ((!strcmp(name, "width") && (n < e->metadata.numeric_range.min || n > 512)) ||
502 (!strcmp(name, "height") && (n < e->metadata.numeric_range.min || n > 256)) ||
503 (!strcmp(name, "snapshot-delay") && (n < 0 || n > 30)) || (!strcmp(name, "fps") && (n < 0 || n > 144)))
504 return SET_ERRNO(ERROR_INVALID_PARAM, "Option %s is out of bounds", name);
505 err = option(r, name, buf);
506 if (err)
507 return err;
508 if (!strcmp(name, "width"))
509 r->options.width = (int)n;
510 else if (!strcmp(name, "height"))
511 r->options.height = (int)n;
512 else if (!strcmp(name, "color-mode")) {
513 if (!strcmp(buf, "16"))
514 r->options.color_mode = TERM_COLOR_16;
515 else if (!strcmp(buf, "256"))
516 r->options.color_mode = TERM_COLOR_256;
517 else if (!strcmp(buf, "truecolor") || !strcmp(buf, "24bit"))
518 r->options.color_mode = TERM_COLOR_TRUECOLOR;
519 else
520 r->options.color_mode = TERM_COLOR_NONE;
521 } else if (!strcmp(name, "palette"))
522 SAFE_STRNCPY(r->options.palette, buf, sizeof(r->options.palette));
523 else if (!strcmp(name, "palette-chars"))
525 }
526 bool synthetic = enabled(opts, "test-pattern"), test_audio = enabled(opts, "test-audio");
527 yyjson_val *audio_device = mcp_get(a, "audio_device"), *volume = mcp_get(a, "volume");
528 if (audio_device) {
529 if (!yyjson_is_int(audio_device) || yyjson_get_sint(audio_device) < -1 || yyjson_get_sint(audio_device) > INT_MAX)
530 return SET_ERRNO(ERROR_INVALID_PARAM, "audio_device must be -1 or a nonnegative device index");
532 SAFE_SNPRINTF(index, sizeof(index), "%d", (int)yyjson_get_sint(audio_device));
533 err = option(r, "microphone-index", index);
534 if (err)
535 return err;
536 }
537 if (volume) {
538 if (!yyjson_is_num(volume) || yyjson_get_num(volume) < 0 || yyjson_get_num(volume) > 1)
539 return SET_ERRNO(ERROR_INVALID_PARAM, "volume must be a number from 0 to 1");
541 bool omitted = false;
542 err = options_json_parse_value("microphone-volume", volume, value, sizeof(value), &omitted);
543 if (err)
544 return err;
545 if (mcp_get(opts, "microphone-volume") || mcp_get(opts, "ivolume"))
546 return SET_ERRNO(ERROR_INVALID_PARAM, "volume conflicts with options.microphone-volume/ivolume");
547 err = option(r, "microphone-volume", value);
548 if (err)
549 return err;
550 }
551 if (tool == MCP_FILE) {
552 if (synthetic || test_audio) {
553 if (mcp_get(a, "path") || mcp_get(a, "data_base64"))
554 return SET_ERRNO(ERROR_INVALID_PARAM, "Synthetic source conflicts with media");
555 if (!synthetic) {
556 option(r, "waveform", "true");
557 option(r, "audio", "true");
558 }
559 } else {
560 err = source_file(p, a, r);
561 if (err)
562 return err;
563 }
564 } else if (tool == MCP_URL) {
565 if (synthetic)
566 return SET_ERRNO(ERROR_INVALID_PARAM, "test-pattern conflicts with URL");
567 err = source_url(p, mcp_get(a, "url"), r);
568 if (err)
569 return err;
570 } else if (tool == MCP_WEBCAM) {
571 r->capture_webcam = !synthetic;
572 bool record_audio =
573 audio_device || volume || enabled(opts, "audio") ||
574 (mcp_get(a, "output_path") && yyjson_get_num(mcp_get(opts, "snapshot-delay")) > 0 && !mcp_get(opts, "audio"));
575 if (record_audio && mcp_get(opts, "audio") && !enabled(opts, "audio"))
576 return SET_ERRNO(ERROR_INVALID_PARAM, "Microphone selection conflicts with options.audio=false");
577 if (record_audio && !test_audio) {
578 option(r, "audio", "true");
579 option(r, "audio-source", "mic");
580 option(r, "audio-capture-source", "mic");
581 }
582 yyjson_val *dev = mcp_get(a, "video_device");
583 if (dev) {
584 if (!yyjson_is_int(dev) || yyjson_get_sint(dev) < 0 || yyjson_get_sint(dev) > 10)
585 return SET_ERRNO(ERROR_INVALID_PARAM, "video_device must be 0–10");
587 SAFE_SNPRINTF(b, sizeof(b), "%d", (int)yyjson_get_sint(dev));
588 option(r, "webcam-index", b);
589 }
590 } else {
591 if (synthetic)
592 return SET_ERRNO(ERROR_INVALID_PARAM, "test-pattern conflicts with audio visualization");
593 yyjson_val *visual = mcp_get(a, "visualization");
594 if (!yyjson_is_str(visual) || (strcmp(yyjson_get_str(visual), "waveform") && strcmp(yyjson_get_str(visual), "fft")))
595 return SET_ERRNO(ERROR_INVALID_PARAM, "visualization must be waveform or fft");
596 option(r, yyjson_get_str(visual), "true");
597 option(r, "audio", "true");
598 yyjson_val *audio = mcp_get(a, "audio");
599 static const char *const audio_fields[] = {"type", "path", "data_base64", "mime_type", "url", NULL};
600 if (audio) {
601 err = check_fields(audio, audio_fields);
602 if (err)
603 return err;
604 }
605 yyjson_val *type = mcp_get(audio, "type");
606 if (type && !yyjson_is_str(type))
607 return SET_ERRNO(ERROR_INVALID_PARAM, "audio.type must be a string");
608 const char *t = type ? yyjson_get_str(type) : (audio_device || volume ? "microphone" : "test");
609 if ((audio_device || volume) && strcmp(t, "microphone"))
610 return SET_ERRNO(ERROR_INVALID_PARAM, "audio_device and volume require microphone audio");
611 if (!strcmp(t, "test")) {
612 if (audio && yyjson_obj_size(audio) > 1)
613 return SET_ERRNO(ERROR_INVALID_PARAM, "Test audio has no source fields");
614 option(r, "test-audio", "true");
615 option(r, "audio-source", "all");
616 } else if (!strcmp(t, "file")) {
617 if (mcp_get(audio, "url") || mcp_get(a, "audio_device") || test_audio)
618 return SET_ERRNO(ERROR_INVALID_PARAM, "Conflicting audio sources");
619 err = source_file(p, audio, r);
620 if (err)
621 return err;
622 option(r, "audio-source", "media");
623 option(r, "audio-capture-source", "media");
624 } else if (!strcmp(t, "url")) {
625 if (mcp_get(audio, "path") || mcp_get(audio, "data_base64") || mcp_get(a, "audio_device") || test_audio)
626 return SET_ERRNO(ERROR_INVALID_PARAM, "Conflicting audio sources");
627 err = source_url(p, mcp_get(audio, "url"), r);
628 if (err)
629 return err;
630 option(r, "audio-source", "media");
631 option(r, "audio-capture-source", "media");
632 } else if (!strcmp(t, "microphone") && !p->restricted) {
633 if (mcp_get(audio, "path") || mcp_get(audio, "data_base64") || mcp_get(audio, "url") || test_audio)
634 return SET_ERRNO(ERROR_INVALID_PARAM, "Conflicting audio sources");
635 option(r, "audio-source", "mic");
636 option(r, "audio-capture-source", "mic");
637 } else
638 return SET_ERRNO(ERROR_INVALID_PARAM, "Unsupported audio source");
639 }
640 if (test_audio)
641 option(r, "audio", "true");
642 if (tool != MCP_WEBCAM && tool != MCP_VISUALIZATION && !test_audio)
643 option(r, "audio-capture-source", "media");
644 yyjson_val *format = mcp_get(a, "output_format");
645 if (format && (!p->restricted || !p->hosted))
646 return SET_ERRNO(ERROR_INVALID_PARAM, "output_format is available only in HTTP server mode");
647 if (p->restricted && p->hosted) {
648 const char *ext =
649 format && yyjson_is_str(format) ? yyjson_get_str(format)
650 : (yyjson_is_false(mcp_get(opts, "snapshot")) || yyjson_get_num(mcp_get(opts, "snapshot-delay")) > 0) ? "mp4"
651 : "png";
652 if ((format && !yyjson_is_str(format)) || !mcp_output_mime(ext) || !output_format_available(ext))
653 return SET_ERRNO(ERROR_INVALID_PARAM, "Unsupported output_format");
654 unsigned char random[16];
655 char token[33], path[PLATFORM_MAX_PATH_LENGTH];
656 randombytes_buf(random, sizeof(random));
657 sodium_bin2hex(token, sizeof(token), random, sizeof(random));
658 int path_len =
659 safe_snprintf(path, sizeof(path), "%s/%lld-%s.%s", p->downloads_dir, (long long)time(NULL) + 1800, token, ext);
660 if (path_len < 0 || (size_t)path_len >= sizeof(path))
661 return SET_ERRNO(ERROR_INVALID_PARAM, "Download path is too long");
662 err = stage_output(path, r);
663 if (err)
664 return err;
665 }
666 yyjson_val *output = mcp_get(a, "output_path");
667 if (output) {
668 if (!yyjson_is_str(output))
669 return SET_ERRNO(ERROR_INVALID_PARAM, "output_path must be a string");
670 err = stage_output(yyjson_get_str(output), r);
671 if (err)
672 return err;
673 }
674 if (output_mode && !strcmp(yyjson_get_str(output_mode), "files") && !r->output_path[0])
675 return SET_ERRNO(ERROR_INVALID_PARAM, "output=files requires output_path");
676 return ASCIICHAT_OK;
677}
#define SET_ERRNO(code,...)
const char * mcp_output_mime(const char *extension)
Definition downloads.c:10
bool enabled
Is filtering active?
Definition grep.c:62
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:507
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
#define PLATFORM_MAX_PATH_LENGTH
Definition common.h:95
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_FILE_OPERATION
@ ERROR_INVALID_PARAM
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
@ HTTP_RENDER_OUTPUT_FILE
@ HTTP_RENDER_OUTPUT_TEXT
@ MCP_VISUALIZATION
Definition mcp.h:34
@ MCP_URL
Definition mcp.h:34
@ MCP_FILE
Definition mcp.h:34
@ MCP_WEBCAM
Definition mcp.h:34
asciichat_error_t options_json_parse_value(const char *name, yyjson_val *v, char *output, size_t output_size, bool *omitted)
static bool size_t index
Definition queries.h:8
#define MCP_SERVER_REQUEST_BUF_BUFFER_SIZE
Definition request.c:3
#define MCP_REQUEST_ARGV_CAPACITY
Definition request.c:9
#define MCP_SERVER_REQUEST_VALUE_BUFFER_SIZE
Definition request.c:5
#define MCP_SERVER_REQUEST_INDEX_BUFFER_SIZE
Definition request.c:4
#define MCP_SERVER_REQUEST_B_BUFFER_SIZE
Definition request.c:6
char palette_chars[OPTIONS_BUFF_SIZE]
char palette[OPTIONS_BUFF_SIZE]
http_render_output_type_t output_type
http_render_options_t options
char output_path[PLATFORM_MAX_PATH_LENGTH]
char downloads_dir[PLATFORM_MAX_PATH_LENGTH]
Definition mcp.h:44
bool restricted
Definition mcp.h:37
bool hosted
Definition mcp.h:36
Registry entry - stores option definition with mode bitmask and metadata.

References http_renderer_process_request_t::argv, ASCIICHAT_OK, http_renderer_process_request_t::capture_webcam, http_render_options_t::color_mode, mcp_policy_t::downloads_dir, enabled, ERROR_FILE_OPERATION, ERROR_INVALID_PARAM, http_render_options_t::height, mcp_policy_t::hosted, HTTP_RENDER_OUTPUT_FILE, HTTP_RENDER_OUTPUT_TEXT, index, MCP_FILE, mcp_output_mime(), MCP_REQUEST_ARGV_CAPACITY, MCP_SERVER_REQUEST_B_BUFFER_SIZE, MCP_SERVER_REQUEST_BUF_BUFFER_SIZE, MCP_SERVER_REQUEST_INDEX_BUFFER_SIZE, MCP_SERVER_REQUEST_VALUE_BUFFER_SIZE, MCP_URL, MCP_VISUALIZATION, MCP_WEBCAM, http_renderer_process_request_t::options, options_json_parse_value(), http_renderer_process_request_t::output_path, http_renderer_process_request_t::output_type, http_render_options_t::palette, http_render_options_t::palette_chars, PLATFORM_MAX_PATH_LENGTH, mcp_policy_t::restricted, SAFE_CALLOC, SAFE_SNPRINTF, safe_snprintf(), SAFE_STRNCPY, SET_ERRNO, http_renderer_process_request_t::timeout_ms, and http_render_options_t::width.

Referenced by mcp_dispatch().

◆ mcp_tools_schema()

yyjson_mut_val * mcp_tools_schema ( yyjson_mut_doc * d,
const mcp_policy_t * p )

Definition at line 158 of file request.c.

158 {
159 const char *names[] = {"render_file", "render_url", "render_webcam", "render_visualization"};
160 const char *descs[] = {
161 "Convert uploaded media or a local file into ASCII art. Source can be omitted for test-pattern/test-audio.",
162 "Convert URL media into ASCII art using the CLI media resolver.",
163 "Capture a local webcam as ASCII art; test-pattern avoids camera access.",
164 "Render waveform or FFT from test audio, uploaded audio, URL audio, or local audio/microphone."};
165 yyjson_mut_val *tools = yyjson_mut_arr(d);
166 for (int i = 0; i < 4; ++i) {
167 if (p->restricted && i == MCP_WEBCAM)
168 continue;
169 yyjson_mut_val *tool = yyjson_mut_obj(d), *s = object_schema(d), *props = yyjson_mut_obj_get(s, "properties");
170 yyjson_mut_obj_add_str(d, tool, "name", names[i]);
171 yyjson_mut_obj_add_str(d, tool, "description", descs[i]);
172 yyjson_mut_obj_add_val(d, props, "options", options_schema(d, p));
173 property(d, s, "timeout_ms", "integer", "Maximum job time, 1000–60000 ms; default 45000.");
174 if (p->restricted && p->hosted) {
175 property(d, s, "output_format", "string",
176 "Output format: png, jpg, jpeg, bmp, webp, gif, mp4, mov, mkv, webm, avi, flv, ogv, ogg or 3gp. "
177 "Defaults to png for a single frame or mp4 for a timed or continuous render. GIF renders media to EOF "
178 "unless snapshot or snapshot-delay is specified.");
179 yyjson_mut_val *available = yyjson_mut_arr(d);
180 for (size_t i = 0; mcp_output_formats[i]; ++i)
181 if (output_format_available(mcp_output_formats[i]))
182 yyjson_mut_arr_add_str(d, available, mcp_output_formats[i]);
183 yyjson_mut_obj_add_val(d, yyjson_mut_obj_get(props, "output_format"), "enum", available);
184 }
185 if (!p->restricted)
186 property(d, s, "output_path", "string",
187 "Save rendered ASCII art as an image/video, selected by extension. Existing files are never replaced.");
188 property(d, s, "output", "string", "Output mode: text or files. Defaults to text unless output_path is supplied.");
189 const char *output_modes[] = {"text", "files", NULL};
190 enum_strings(d, yyjson_mut_obj_get(props, "output"), output_modes);
191 if (i == MCP_FILE)
192 file_properties(d, s, !p->restricted);
193 if (i == MCP_URL) {
194 property(d, s, "url", "string", "HTTP/HTTPS media URL (local profile also accepts RTSP/RTMP).");
195 yyjson_mut_val *required = yyjson_mut_arr(d);
196 yyjson_mut_arr_add_str(d, required, "url");
197 yyjson_mut_obj_add_val(d, s, "required", required);
198 }
199 if (i == MCP_WEBCAM) {
200 property(d, s, "video_device", "integer", "Local webcam index, 0–10; default 0.");
201 yyjson_mut_val *device = yyjson_mut_obj_get(props, "video_device");
202 yyjson_mut_obj_add_int(d, device, "default", 0);
203 yyjson_mut_obj_add_int(d, device, "minimum", 0);
204 yyjson_mut_obj_add_int(d, device, "maximum", 10);
205 }
206 if (!p->restricted && (i == MCP_WEBCAM || i == MCP_VISUALIZATION)) {
207 property(d, s, "audio_device", "integer", "Microphone index; -1 or omitted selects the system default.");
208 yyjson_mut_val *device = yyjson_mut_obj_get(props, "audio_device");
209 yyjson_mut_obj_add_int(d, device, "default", -1);
210 yyjson_mut_obj_add_int(d, device, "minimum", -1);
211 yyjson_mut_obj_add_int(d, device, "maximum", INT_MAX);
212 yyjson_mut_val *volume = options_json_schema_for_option(d, "microphone-volume");
213 yyjson_mut_obj_remove_str(volume, "description");
214 yyjson_mut_obj_add_str(d, volume, "description", "Microphone volume multiplier, 0–1; 0 mutes, 1 is normal.");
215 yyjson_mut_obj_add_real(d, volume, "default", 1);
216 yyjson_mut_obj_add_real(d, volume, "minimum", 0);
217 yyjson_mut_obj_add_real(d, volume, "maximum", 1);
218 yyjson_mut_obj_add_val(d, props, "volume", volume);
219 }
220 if (i == MCP_VISUALIZATION) {
221 property(d, s, "visualization", "string", "waveform or fft");
222 const char *values[] = {"waveform", "fft", NULL};
223 enum_strings(d, yyjson_mut_obj_get(props, "visualization"), values);
224 yyjson_mut_val *audio = object_schema(d);
225 property(d, audio, "type", "string", "Audio source, default test.");
226 const char *types[] = {"test", "file", "url", "microphone", NULL};
227 const char *remote_types[] = {"test", "file", "url", NULL};
228 enum_strings(d, yyjson_mut_obj_get(yyjson_mut_obj_get(audio, "properties"), "type"),
229 p->restricted ? remote_types : types);
230 file_properties(d, audio, !p->restricted);
231 property(d, audio, "url", "string", "Audio URL.");
232 yyjson_mut_obj_add_val(d, props, "audio", audio);
233 yyjson_mut_val *req = yyjson_mut_arr(d);
234 yyjson_mut_arr_add_str(d, req, "visualization");
235 yyjson_mut_obj_add_val(d, s, "required", req);
236 }
237 yyjson_mut_val *out = object_schema(d);
238 property(d, out, "ascii", "string", "Rendered text; saved media may have no terminal text.");
239 property(d, out, "elapsed_ms", "integer", "Worker wall-clock duration.");
240 if (p->restricted && p->hosted) {
241 property(d, out, "download_url", "string", "Temporary URL for the rendered file.");
242 property(d, out, "expires_at", "string", "UTC expiration timestamp in RFC 3339 format.");
243 }
244 yyjson_mut_val *saved = yyjson_mut_obj(d), *items = yyjson_mut_obj(d);
245 yyjson_mut_obj_add_str(d, saved, "type", "array");
246 yyjson_mut_obj_add_str(d, items, "type", "string");
247 yyjson_mut_obj_add_val(d, saved, "items", items);
248 yyjson_mut_obj_add_val(d, yyjson_mut_obj_get(out, "properties"), "saved_files", saved);
249 yyjson_mut_val *required = yyjson_mut_arr(d);
250 yyjson_mut_arr_add_str(d, required, "ascii");
251 yyjson_mut_arr_add_str(d, required, "elapsed_ms");
252 yyjson_mut_arr_add_str(d, required, "saved_files");
253 yyjson_mut_obj_add_val(d, out, "required", required);
254 yyjson_mut_obj_add_val(d, tool, "outputSchema", out);
255 yyjson_mut_obj_add_val(d, tool, "inputSchema", s);
256 yyjson_mut_arr_add_val(tools, tool);
257 }
258 if (!p->restricted) {
259 yyjson_mut_val *tool = yyjson_mut_obj(d);
260 yyjson_mut_obj_add_str(d, tool, "name", "device_list");
261 yyjson_mut_obj_add_str(d, tool, "description",
262 "List local video and microphone device indices, names, and defaults.");
263 yyjson_mut_obj_add_val(d, tool, "inputSchema", object_schema(d));
264 yyjson_mut_val *annotations = yyjson_mut_obj(d);
265 yyjson_mut_obj_add_bool(d, annotations, "readOnlyHint", true);
266 yyjson_mut_obj_add_val(d, tool, "annotations", annotations);
267 yyjson_mut_arr_add_val(tools, tool);
268 }
269 return tools;
270}
const char *const mcp_output_formats[]
Definition downloads.c:7
yyjson_mut_val * options_json_schema_for_option(yyjson_mut_doc *d, const char *name)
static bool size_t gotel_query_t * out
Definition queries.h:8

References mcp_policy_t::hosted, MCP_FILE, mcp_output_formats, MCP_URL, MCP_VISUALIZATION, MCP_WEBCAM, options_json_schema_for_option(), out, and mcp_policy_t::restricted.

Referenced by mcp_dispatch().