veryfront 0.1.13 → 0.1.15

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.
Files changed (153) hide show
  1. package/esm/cli/app/data/slug-words.d.ts.map +1 -1
  2. package/esm/cli/app/data/slug-words.js +225 -90
  3. package/esm/cli/app/operations/project-creation.js +4 -3
  4. package/esm/cli/app/shell.js +1 -1
  5. package/esm/cli/app/utils.d.ts +5 -4
  6. package/esm/cli/app/utils.d.ts.map +1 -1
  7. package/esm/cli/app/utils.js +0 -23
  8. package/esm/cli/app/views/dashboard.d.ts +1 -1
  9. package/esm/cli/app/views/dashboard.d.ts.map +1 -1
  10. package/esm/cli/app/views/dashboard.js +22 -4
  11. package/esm/cli/auth/callback-server.d.ts.map +1 -1
  12. package/esm/cli/auth/callback-server.js +3 -2
  13. package/esm/cli/commands/dev/handler.d.ts.map +1 -1
  14. package/esm/cli/commands/dev/handler.js +2 -0
  15. package/esm/cli/commands/init/init-command.d.ts.map +1 -1
  16. package/esm/cli/commands/init/init-command.js +20 -3
  17. package/esm/cli/commands/init/interactive-wizard.d.ts +3 -2
  18. package/esm/cli/commands/init/interactive-wizard.d.ts.map +1 -1
  19. package/esm/cli/commands/init/interactive-wizard.js +55 -27
  20. package/esm/cli/mcp/remote-file-tools.d.ts +0 -6
  21. package/esm/cli/mcp/remote-file-tools.d.ts.map +1 -1
  22. package/esm/cli/mcp/remote-file-tools.js +37 -15
  23. package/esm/cli/shared/reserve-slug.d.ts.map +1 -1
  24. package/esm/cli/shared/reserve-slug.js +8 -3
  25. package/esm/cli/utils/env-prompt.d.ts.map +1 -1
  26. package/esm/cli/utils/env-prompt.js +3 -0
  27. package/esm/deno.d.ts +5 -1
  28. package/esm/deno.js +11 -4
  29. package/esm/src/agent/chat-handler.d.ts +4 -3
  30. package/esm/src/agent/chat-handler.d.ts.map +1 -1
  31. package/esm/src/agent/chat-handler.js +55 -4
  32. package/esm/src/agent/react/index.d.ts +1 -1
  33. package/esm/src/agent/react/index.d.ts.map +1 -1
  34. package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts +18 -0
  35. package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts.map +1 -0
  36. package/esm/src/agent/react/use-chat/browser-inference/browser-engine.js +54 -0
  37. package/esm/src/agent/react/use-chat/browser-inference/types.d.ts +43 -0
  38. package/esm/src/agent/react/use-chat/browser-inference/types.d.ts.map +1 -0
  39. package/esm/src/agent/react/use-chat/browser-inference/types.js +4 -0
  40. package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts +23 -0
  41. package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts.map +1 -0
  42. package/esm/src/agent/react/use-chat/browser-inference/worker-client.js +67 -0
  43. package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts +8 -0
  44. package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts.map +1 -0
  45. package/esm/src/agent/react/use-chat/browser-inference/worker-script.js +97 -0
  46. package/esm/src/agent/react/use-chat/index.d.ts +1 -1
  47. package/esm/src/agent/react/use-chat/index.d.ts.map +1 -1
  48. package/esm/src/agent/react/use-chat/types.d.ts +12 -0
  49. package/esm/src/agent/react/use-chat/types.d.ts.map +1 -1
  50. package/esm/src/agent/react/use-chat/use-chat.d.ts.map +1 -1
  51. package/esm/src/agent/react/use-chat/use-chat.js +120 -6
  52. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  53. package/esm/src/agent/runtime/index.js +59 -7
  54. package/esm/src/build/production-build/templates.d.ts +2 -2
  55. package/esm/src/build/production-build/templates.d.ts.map +1 -1
  56. package/esm/src/build/production-build/templates.js +2 -68
  57. package/esm/src/chat/index.d.ts +1 -1
  58. package/esm/src/chat/index.d.ts.map +1 -1
  59. package/esm/src/errors/veryfront-error.d.ts +3 -0
  60. package/esm/src/errors/veryfront-error.d.ts.map +1 -1
  61. package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -1
  62. package/esm/src/platform/adapters/runtime/deno/adapter.js +24 -3
  63. package/esm/src/platform/compat/http/deno-server.d.ts.map +1 -1
  64. package/esm/src/platform/compat/http/deno-server.js +23 -2
  65. package/esm/src/provider/index.d.ts +1 -1
  66. package/esm/src/provider/index.d.ts.map +1 -1
  67. package/esm/src/provider/index.js +1 -1
  68. package/esm/src/provider/local/ai-sdk-adapter.d.ts +19 -0
  69. package/esm/src/provider/local/ai-sdk-adapter.d.ts.map +1 -0
  70. package/esm/src/provider/local/ai-sdk-adapter.js +164 -0
  71. package/esm/src/provider/local/env.d.ts +10 -0
  72. package/esm/src/provider/local/env.d.ts.map +1 -0
  73. package/esm/src/provider/local/env.js +23 -0
  74. package/esm/src/provider/local/local-engine.d.ts +61 -0
  75. package/esm/src/provider/local/local-engine.d.ts.map +1 -0
  76. package/esm/src/provider/local/local-engine.js +211 -0
  77. package/esm/src/provider/local/model-catalog.d.ts +30 -0
  78. package/esm/src/provider/local/model-catalog.d.ts.map +1 -0
  79. package/esm/src/provider/local/model-catalog.js +58 -0
  80. package/esm/src/provider/model-registry.d.ts +14 -0
  81. package/esm/src/provider/model-registry.d.ts.map +1 -1
  82. package/esm/src/provider/model-registry.js +58 -2
  83. package/esm/src/proxy/main.js +34 -6
  84. package/esm/src/proxy/server-resolver.d.ts +23 -0
  85. package/esm/src/proxy/server-resolver.d.ts.map +1 -0
  86. package/esm/src/proxy/server-resolver.js +124 -0
  87. package/esm/src/react/components/ai/chat/components/inference-badge.d.ts +8 -0
  88. package/esm/src/react/components/ai/chat/components/inference-badge.d.ts.map +1 -0
  89. package/esm/src/react/components/ai/chat/components/inference-badge.js +36 -0
  90. package/esm/src/react/components/ai/chat/components/upgrade-cta.d.ts +7 -0
  91. package/esm/src/react/components/ai/chat/components/upgrade-cta.d.ts.map +1 -0
  92. package/esm/src/react/components/ai/chat/components/upgrade-cta.js +33 -0
  93. package/esm/src/react/components/ai/chat/index.d.ts +7 -1
  94. package/esm/src/react/components/ai/chat/index.d.ts.map +1 -1
  95. package/esm/src/react/components/ai/chat/index.js +16 -4
  96. package/esm/src/sandbox/index.d.ts +31 -0
  97. package/esm/src/sandbox/index.d.ts.map +1 -0
  98. package/esm/src/sandbox/index.js +30 -0
  99. package/esm/src/sandbox/sandbox.d.ts +48 -0
  100. package/esm/src/sandbox/sandbox.d.ts.map +1 -0
  101. package/esm/src/sandbox/sandbox.js +178 -0
  102. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.d.ts.map +1 -1
  103. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.js +8 -2
  104. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.d.ts +1 -0
  105. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.d.ts.map +1 -1
  106. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.js +1 -0
  107. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.d.ts.map +1 -1
  108. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.js +15 -1
  109. package/package.json +8 -1
  110. package/src/cli/app/data/slug-words.ts +225 -90
  111. package/src/cli/app/operations/project-creation.ts +3 -3
  112. package/src/cli/app/shell.ts +1 -1
  113. package/src/cli/app/utils.ts +0 -30
  114. package/src/cli/app/views/dashboard.ts +27 -4
  115. package/src/cli/auth/callback-server.ts +3 -2
  116. package/src/cli/commands/dev/handler.ts +2 -0
  117. package/src/cli/commands/init/init-command.ts +30 -3
  118. package/src/cli/commands/init/interactive-wizard.ts +62 -34
  119. package/src/cli/mcp/remote-file-tools.ts +50 -15
  120. package/src/cli/shared/reserve-slug.ts +9 -2
  121. package/src/cli/utils/env-prompt.ts +3 -0
  122. package/src/deno.js +11 -4
  123. package/src/src/agent/chat-handler.ts +57 -4
  124. package/src/src/agent/react/index.ts +2 -0
  125. package/src/src/agent/react/use-chat/browser-inference/browser-engine.ts +81 -0
  126. package/src/src/agent/react/use-chat/browser-inference/types.ts +52 -0
  127. package/src/src/agent/react/use-chat/browser-inference/worker-client.ts +89 -0
  128. package/src/src/agent/react/use-chat/browser-inference/worker-script.ts +98 -0
  129. package/src/src/agent/react/use-chat/index.ts +2 -0
  130. package/src/src/agent/react/use-chat/types.ts +20 -0
  131. package/src/src/agent/react/use-chat/use-chat.ts +148 -8
  132. package/src/src/agent/runtime/index.ts +72 -6
  133. package/src/src/build/production-build/templates.ts +2 -68
  134. package/src/src/chat/index.ts +2 -0
  135. package/src/src/errors/veryfront-error.ts +2 -1
  136. package/src/src/platform/adapters/runtime/deno/adapter.ts +25 -3
  137. package/src/src/platform/compat/http/deno-server.ts +28 -1
  138. package/src/src/provider/index.ts +1 -0
  139. package/src/src/provider/local/ai-sdk-adapter.ts +207 -0
  140. package/src/src/provider/local/env.ts +26 -0
  141. package/src/src/provider/local/local-engine.ts +288 -0
  142. package/src/src/provider/local/model-catalog.ts +73 -0
  143. package/src/src/provider/model-registry.ts +66 -2
  144. package/src/src/proxy/main.ts +41 -6
  145. package/src/src/proxy/server-resolver.ts +151 -0
  146. package/src/src/react/components/ai/chat/components/inference-badge.tsx +48 -0
  147. package/src/src/react/components/ai/chat/components/upgrade-cta.tsx +56 -0
  148. package/src/src/react/components/ai/chat/index.tsx +43 -6
  149. package/src/src/sandbox/index.ts +32 -0
  150. package/src/src/sandbox/sandbox.ts +236 -0
  151. package/src/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.ts +9 -2
  152. package/src/src/transforms/pipeline/stages/ssr-vf-modules/index.ts +1 -0
  153. package/src/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts +17 -0
@@ -5,39 +5,9 @@
5
5
  */
6
6
 
7
7
  /**
8
- * Client-side CSS styles for loading states, error display, and prose formatting
8
+ * Client-side CSS styles for error display in production builds
9
9
  */
10
- export const CLIENT_STYLES = `body {
11
- margin: 0;
12
- font-family:
13
- -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
14
- line-height: 1.5;
15
- }
16
-
17
- .loading-container {
18
- display: flex;
19
- justify-content: center;
20
- align-items: center;
21
- min-height: 100vh;
22
- background: #f9fafb;
23
- }
24
-
25
- .loading-spinner {
26
- width: 40px;
27
- height: 40px;
28
- border: 3px solid #e5e7eb;
29
- border-top-color: #3b82f6;
30
- border-radius: 50%;
31
- animation: spin 1s linear infinite;
32
- }
33
-
34
- @keyframes spin {
35
- to {
36
- transform: rotate(360deg);
37
- }
38
- }
39
-
40
- .error-container {
10
+ export const CLIENT_STYLES = `.error-container {
41
11
  max-width: 600px;
42
12
  margin: 2rem auto;
43
13
  padding: 2rem;
@@ -45,42 +15,6 @@ export const CLIENT_STYLES = `body {
45
15
  border: 1px solid #fcc;
46
16
  border-radius: 8px;
47
17
  color: #c00;
48
- }
49
-
50
- .prose {
51
- max-width: 65ch;
52
- margin: 0 auto;
53
- padding: 2rem;
54
- }
55
-
56
- .prose h1, .prose h2, .prose h3 {
57
- margin-top: 2em;
58
- margin-bottom: 1em;
59
- }
60
-
61
- .prose p {
62
- margin-bottom: 1.5em;
63
- }
64
-
65
- .prose code {
66
- background: #f3f4f6;
67
- padding: 0.2em 0.4em;
68
- border-radius: 3px;
69
- font-size: 0.875em;
70
- }
71
-
72
- .prose pre {
73
- background: #1f2937;
74
- color: #f9fafb;
75
- padding: 1em;
76
- border-radius: 8px;
77
- overflow-x: auto;
78
- }
79
-
80
- .prose pre code {
81
- background: transparent;
82
- padding: 0;
83
- color: inherit;
84
18
  }`;
85
19
 
86
20
  /**
@@ -98,7 +98,9 @@ export type { AgentTheme, ChatTheme } from "../react/components/ai/theme.js";
98
98
 
99
99
  export { useChat } from "../agent/react/use-chat/index.js";
100
100
  export type {
101
+ BrowserInferenceStatus,
101
102
  DynamicToolUIPart,
103
+ InferenceMode,
102
104
  OnToolCallArg,
103
105
  ReasoningUIPart,
104
106
  TextUIPart,
@@ -75,7 +75,8 @@ export type VeryfrontErrorData =
75
75
  | { type: "file"; message: string; context?: FileContext }
76
76
  | { type: "network"; message: string; context?: NetworkContext }
77
77
  | { type: "permission"; message: string; context?: FileContext }
78
- | { type: "not_supported"; message: string; feature?: string };
78
+ | { type: "not_supported"; message: string; feature?: string }
79
+ | { type: "no_ai_available"; message: string };
79
80
 
80
81
  export function createError(error: VeryfrontErrorData): VeryfrontErrorData {
81
82
  return error;
@@ -393,16 +393,38 @@ export class DenoAdapter implements RuntimeAdapter {
393
393
  }
394
394
  : handler;
395
395
 
396
- const server = dntShim.Deno.serve({
396
+ // Access native Deno.serve via `self` to bypass dnt shim transform.
397
+ // dnt rewrites both `Deno.*` and `globalThis.*` to use @deno/shim-deno which lacks .serve.
398
+ // `self` is not shimmed by dnt and equals `globalThis` in Deno.
399
+ const nativeDeno = (self as unknown as Record<string, typeof dntShim.Deno>)["Deno"]!;
400
+
401
+ // Access native Response via `self` to bypass dnt shim transform.
402
+ // In npm packages, dnt replaces Response with undici's polyfill,
403
+ // but Deno.serve requires native Response instances.
404
+ const NativeResponse = (self as unknown as { Response: typeof dntShim.Response })
405
+ .Response;
406
+
407
+ const server = nativeDeno.serve({
397
408
  port,
398
409
  hostname,
399
410
  signal,
400
411
  handler: async (request) => {
401
412
  try {
402
- return await wrappedHandler(request);
413
+ const response: dntShim.Response = await wrappedHandler(request);
414
+ // If already native (compiled binary), return as-is
415
+ if (response instanceof NativeResponse) return response;
416
+ // Re-wrap polyfilled Response as native Response.
417
+ // dnt replaces Response with undici's polyfill which fails
418
+ // Deno.serve's native instanceof check.
419
+ const r = response as unknown as dntShim.Response;
420
+ return new NativeResponse(r.body, {
421
+ status: r.status,
422
+ statusText: r.statusText,
423
+ headers: r.headers,
424
+ });
403
425
  } catch (error) {
404
426
  serverLogger.error("Request handler error:", error);
405
- return new dntShim.Response("Internal Server Error", { status: 500 });
427
+ return new NativeResponse("Internal Server Error", { status: 500 });
406
428
  }
407
429
  },
408
430
  onListen: (params) => {
@@ -13,7 +13,34 @@ export class DenoHttpServer implements HttpServer {
13
13
 
14
14
  onListen?.({ hostname, port });
15
15
 
16
- await dntShim.Deno.serve({ port, hostname, signal: serveSignal }, handler);
16
+ // Access native Deno.serve via `self` to bypass dnt shim transform.
17
+ const nativeDeno = (self as unknown as Record<string, typeof dntShim.Deno>)["Deno"]!;
18
+
19
+ // Access native Response via `self` to bypass dnt shim transform.
20
+ // In npm packages, dnt replaces Response with undici's polyfill,
21
+ // but Deno.serve requires native Response instances.
22
+ const NativeResponse = (self as unknown as { Response: typeof dntShim.Response })
23
+ .Response;
24
+
25
+ const wrappedHandler: Handler = async (req) => {
26
+ const response: dntShim.Response = await handler(req);
27
+ // If already native (compiled binary or WebSocket upgrade), return as-is
28
+ if (response instanceof NativeResponse) return response;
29
+ // Re-wrap polyfilled Response as native Response.
30
+ // At runtime, `response` may be an undici Response (from dnt shim) that
31
+ // fails Deno's native instanceof check. Cast to access its properties.
32
+ const r = response as unknown as dntShim.Response;
33
+ return new NativeResponse(r.body, {
34
+ status: r.status,
35
+ statusText: r.statusText,
36
+ headers: r.headers,
37
+ });
38
+ };
39
+
40
+ await nativeDeno.serve(
41
+ { port, hostname, signal: serveSignal },
42
+ wrappedHandler,
43
+ );
17
44
  }
18
45
 
19
46
  close(): Promise<void> {
@@ -20,6 +20,7 @@ import "../../_dnt.polyfills.js";
20
20
 
21
21
  export {
22
22
  clearModelProviders,
23
+ ensureModelReady,
23
24
  getRegisteredModelProviders,
24
25
  hasModelProvider,
25
26
  registerModelProvider,
@@ -0,0 +1,207 @@
1
+ /**
2
+ * AI SDK Adapter for Local Models
3
+ *
4
+ * Bridges `@huggingface/transformers` local inference to the AI SDK
5
+ * `LanguageModelV2` interface. This allows `streamText()` and
6
+ * `generateText()` to work with local models seamlessly.
7
+ *
8
+ * @module provider/local
9
+ */
10
+
11
+ import type { LanguageModel } from "ai";
12
+ import { generate, generateStream } from "./local-engine.js";
13
+ import type { ChatMessage, GenerateOptions } from "./local-engine.js";
14
+ import { DEFAULT_LOCAL_MODEL } from "./model-catalog.js";
15
+ import { serverLogger } from "../../utils/index.js";
16
+ import { createError, fromError, toError } from "../../errors/veryfront-error.js";
17
+ import { isLocalAIDisabled } from "./env.js";
18
+
19
+ const logger = serverLogger.component("local-llm");
20
+
21
+ /**
22
+ * Convert AI SDK LanguageModelV2 prompt format to simple ChatMessage array.
23
+ *
24
+ * The AI SDK prompt is an array of message objects with role and content arrays.
25
+ * We extract text content for the local model.
26
+ */
27
+ // deno-lint-ignore no-explicit-any
28
+ function convertPrompt(prompt: any[]): ChatMessage[] {
29
+ const messages: ChatMessage[] = [];
30
+
31
+ for (const msg of prompt) {
32
+ const role = msg.role as "system" | "user" | "assistant" | "tool";
33
+ // Skip tool messages — local models don't support tool calling
34
+ if (role === "tool") continue;
35
+
36
+ const mappedRole = role === "system" ? "system" : role === "user" ? "user" : "assistant";
37
+
38
+ // Extract text content from content array
39
+ let text = "";
40
+ if (typeof msg.content === "string") {
41
+ text = msg.content;
42
+ } else if (Array.isArray(msg.content)) {
43
+ for (const part of msg.content) {
44
+ if (part.type === "text" && typeof part.text === "string") {
45
+ text += part.text;
46
+ }
47
+ }
48
+ }
49
+
50
+ if (text) {
51
+ messages.push({ role: mappedRole, content: text });
52
+ }
53
+ }
54
+
55
+ return messages;
56
+ }
57
+
58
+ /**
59
+ * Create a local AI SDK LanguageModel for the given model ID.
60
+ *
61
+ * The returned object implements the LanguageModelV2 interface, making it
62
+ * compatible with all AI SDK functions (`streamText`, `generateText`, etc.)
63
+ * and all VeryFront hooks (`useChat`).
64
+ */
65
+ export function createLocalModel(modelId?: string): LanguageModel {
66
+ const resolvedId = modelId || DEFAULT_LOCAL_MODEL;
67
+
68
+ const model = {
69
+ /** Marker so ensureModelReady() can distinguish real local-engine models
70
+ * from mock/custom providers that happen to use provider:"local". */
71
+ _isVfLocalModel: true as const,
72
+ specificationVersion: "v2" as const,
73
+ provider: "local",
74
+ modelId: `local/${resolvedId}`,
75
+
76
+ supportedUrls: {},
77
+
78
+ async doGenerate(options: {
79
+ prompt: unknown[];
80
+ maxOutputTokens?: number;
81
+ temperature?: number;
82
+ topP?: number;
83
+ topK?: number;
84
+ stopSequences?: string[];
85
+ }) {
86
+ const messages = convertPrompt(options.prompt as unknown[]);
87
+ const genOptions: GenerateOptions = {
88
+ maxNewTokens: options.maxOutputTokens ?? 512,
89
+ temperature: options.temperature ?? 0.7,
90
+ topP: options.topP,
91
+ topK: options.topK,
92
+ stopSequences: options.stopSequences,
93
+ };
94
+
95
+ logger.debug(`[local] doGenerate: ${messages.length} messages → ${resolvedId}`);
96
+
97
+ const text = await generate(resolvedId, messages, genOptions);
98
+
99
+ return {
100
+ content: [{ type: "text" as const, text }],
101
+ finishReason: "stop" as const,
102
+ usage: {
103
+ inputTokens: undefined,
104
+ outputTokens: undefined,
105
+ totalTokens: undefined,
106
+ },
107
+ warnings: [],
108
+ };
109
+ },
110
+
111
+ async doStream(options: {
112
+ prompt: unknown[];
113
+ maxOutputTokens?: number;
114
+ temperature?: number;
115
+ topP?: number;
116
+ topK?: number;
117
+ stopSequences?: string[];
118
+ }) {
119
+ // Eagerly check if local AI is disabled — must throw before creating the
120
+ // ReadableStream, otherwise the 200 response headers are already committed.
121
+ // Note: getTransformers() in local-engine.ts also checks this, but we need
122
+ // the check here too because doStream creates a ReadableStream wrapper and
123
+ // errors inside it would be swallowed as in-band stream errors.
124
+ if (isLocalAIDisabled()) {
125
+ throw toError(
126
+ createError({
127
+ type: "no_ai_available",
128
+ message: "Local AI disabled via VERYFRONT_DISABLE_LOCAL_AI environment variable.",
129
+ }),
130
+ );
131
+ }
132
+
133
+ const messages = convertPrompt(options.prompt as unknown[]);
134
+ const genOptions: GenerateOptions = {
135
+ maxNewTokens: options.maxOutputTokens ?? 512,
136
+ temperature: options.temperature ?? 0.7,
137
+ topP: options.topP,
138
+ topK: options.topK,
139
+ stopSequences: options.stopSequences,
140
+ };
141
+
142
+ logger.debug(`[local] doStream: ${messages.length} messages → ${resolvedId}`);
143
+
144
+ const textId = `text-${Date.now()}`;
145
+
146
+ const stream = new ReadableStream({
147
+ async start(controller) {
148
+ try {
149
+ // Emit stream-start
150
+ controller.enqueue({ type: "stream-start", warnings: [] });
151
+
152
+ // Emit response metadata
153
+ controller.enqueue({
154
+ type: "response-metadata",
155
+ id: `local-${Date.now()}`,
156
+ timestamp: new Date(),
157
+ modelId: `local/${resolvedId}`,
158
+ });
159
+
160
+ // Emit text-start
161
+ controller.enqueue({ type: "text-start", id: textId });
162
+
163
+ // Stream tokens
164
+ for await (const token of generateStream(resolvedId, messages, genOptions)) {
165
+ controller.enqueue({
166
+ type: "text-delta",
167
+ id: textId,
168
+ delta: token,
169
+ });
170
+ }
171
+
172
+ // Emit text-end
173
+ controller.enqueue({ type: "text-end", id: textId });
174
+
175
+ // Emit finish
176
+ controller.enqueue({
177
+ type: "finish",
178
+ finishReason: "stop",
179
+ usage: {
180
+ inputTokens: undefined,
181
+ outputTokens: undefined,
182
+ totalTokens: undefined,
183
+ },
184
+ });
185
+
186
+ controller.close();
187
+ } catch (error) {
188
+ // Let no_ai_available propagate — the chat handler needs it
189
+ // for a proper 503 response instead of a 200 with in-band error.
190
+ const vfError = fromError(error);
191
+ if (vfError?.type === "no_ai_available") throw error;
192
+
193
+ controller.enqueue({
194
+ type: "error",
195
+ error: error instanceof Error ? error : new Error(String(error)),
196
+ });
197
+ controller.close();
198
+ }
199
+ },
200
+ });
201
+
202
+ return { stream };
203
+ },
204
+ };
205
+
206
+ return model as LanguageModel;
207
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Cross-platform environment helpers for local AI provider.
3
+ *
4
+ * Abstracts Deno/Node env access so all local-AI checks go through
5
+ * a single function — no duplicated `(globalThis as any).Deno?.env` patterns.
6
+ *
7
+ * @module provider/local
8
+ */
9
+
10
+ /**
11
+ * Check whether local AI is explicitly disabled via environment variable.
12
+ * Works in Deno, Node, and compiled binaries.
13
+ */
14
+ import * as dntShim from "../../../_dnt.shims.js";
15
+
16
+ export function isLocalAIDisabled(): boolean {
17
+ // deno-lint-ignore no-explicit-any
18
+ const denoVal = (dntShim.dntGlobalThis as any).Deno?.env?.get?.("VERYFRONT_DISABLE_LOCAL_AI");
19
+ if (denoVal === "1") return true;
20
+
21
+ if (typeof process !== "undefined" && process.env?.VERYFRONT_DISABLE_LOCAL_AI === "1") {
22
+ return true;
23
+ }
24
+
25
+ return false;
26
+ }