cmd.go 45.7 KB
Newer Older
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1
2
3
package cmd

import (
Michael Yang's avatar
Michael Yang committed
4
	"bufio"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
5
	"context"
6
7
	"crypto/ed25519"
	"crypto/rand"
8
	"encoding/json"
9
	"encoding/pem"
Michael Yang's avatar
Michael Yang committed
10
	"errors"
Bruce MacDonald's avatar
Bruce MacDonald committed
11
	"fmt"
Michael Yang's avatar
Michael Yang committed
12
	"io"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
13
	"log"
14
	"math"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
15
	"net"
16
	"net/http"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
17
	"os"
18
	"os/signal"
19
	"path/filepath"
20
	"runtime"
21
	"slices"
22
	"sort"
Michael Yang's avatar
Michael Yang committed
23
	"strconv"
Michael Yang's avatar
Michael Yang committed
24
	"strings"
25
	"sync/atomic"
26
	"syscall"
Michael Yang's avatar
Michael Yang committed
27
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
28

29
	"github.com/containerd/console"
30
	"github.com/mattn/go-runewidth"
Patrick Devine's avatar
Patrick Devine committed
31
	"github.com/olekukonko/tablewriter"
Michael Yang's avatar
Michael Yang committed
32
	"github.com/spf13/cobra"
33
	"golang.org/x/crypto/ssh"
34
	"golang.org/x/sync/errgroup"
35
	"golang.org/x/term"
Michael Yang's avatar
Michael Yang committed
36

37
	"github.com/ollama/ollama/api"
38
	"github.com/ollama/ollama/envconfig"
39
	"github.com/ollama/ollama/format"
40
	"github.com/ollama/ollama/parser"
41
	"github.com/ollama/ollama/progress"
42
	"github.com/ollama/ollama/readline"
Jesse Gross's avatar
Jesse Gross committed
43
	"github.com/ollama/ollama/runner"
44
	"github.com/ollama/ollama/server"
45
	"github.com/ollama/ollama/types/model"
46
	"github.com/ollama/ollama/types/syncmap"
47
	"github.com/ollama/ollama/version"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
48
49
)

50
const ConnectInstructions = "To sign in, navigate to:\n    %s\n\n"
51

52
53
54
55
56
57
58
59
60
// ensureThinkingSupport emits a warning if the model does not advertise thinking support
func ensureThinkingSupport(ctx context.Context, client *api.Client, name string) {
	if name == "" {
		return
	}
	resp, err := client.Show(ctx, &api.ShowRequest{Model: name})
	if err != nil {
		return
	}
61
62
	if slices.Contains(resp.Capabilities, model.CapabilityThinking) {
		return
63
64
65
66
	}
	fmt.Fprintf(os.Stderr, "warning: model %q does not support thinking output\n", name)
}

67
var errModelfileNotFound = errors.New("specified Modelfile wasn't found")
68
69

func getModelfileName(cmd *cobra.Command) (string, error) {
70
	filename, _ := cmd.Flags().GetString("file")
71
72
73
74
75
76

	if filename == "" {
		filename = "Modelfile"
	}

	absName, err := filepath.Abs(filename)
77
	if err != nil {
78
		return "", err
79
80
	}

81
	_, err = os.Stat(absName)
82
	if err != nil {
83
		return "", err
84
	}
85

86
87
88
89
	return absName, nil
}

func CreateHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
90
91
92
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

93
94
95
96
97
98
99
100
101
102
	var reader io.Reader

	filename, err := getModelfileName(cmd)
	if os.IsNotExist(err) {
		if filename == "" {
			reader = strings.NewReader("FROM .\n")
		} else {
			return errModelfileNotFound
		}
	} else if err != nil {
Michael Yang's avatar
Michael Yang committed
103
		return err
104
105
106
107
108
109
110
111
	} else {
		f, err := os.Open(filename)
		if err != nil {
			return err
		}

		reader = f
		defer f.Close()
Michael Yang's avatar
Michael Yang committed
112
113
	}

114
	modelfile, err := parser.ParseFile(reader)
Michael Yang's avatar
Michael Yang committed
115
116
117
118
	if err != nil {
		return err
	}

119
120
121
122
	status := "gathering model components"
	spinner := progress.NewSpinner(status)
	p.Add(status, spinner)

123
	req, err := modelfile.CreateRequest(filepath.Dir(filename))
Michael Yang's avatar
Michael Yang committed
124
125
126
	if err != nil {
		return err
	}
127
	spinner.Stop()
Michael Yang's avatar
Michael Yang committed
128

129
	req.Model = args[0]
130
131
132
133
	quantize, _ := cmd.Flags().GetString("quantize")
	if quantize != "" {
		req.Quantize = quantize
	}
134

135
136
137
138
139
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

140
141
142
143
144
145
	var g errgroup.Group
	g.SetLimit(max(runtime.GOMAXPROCS(0)-1, 1))

	files := syncmap.NewSyncMap[string, string]()
	for f, digest := range req.Files {
		g.Go(func() error {
146
			if _, err := createBlob(cmd, client, f, digest, p); err != nil {
Michael Yang's avatar
Michael Yang committed
147
148
				return err
			}
149
150
151
152
153
154
155

			// TODO: this is incorrect since the file might be in a subdirectory
			//       instead this should take the path relative to the model directory
			//       but the current implementation does not allow this
			files.Store(filepath.Base(f), digest)
			return nil
		})
156
	}
Michael Yang's avatar
Michael Yang committed
157

158
159
160
	adapters := syncmap.NewSyncMap[string, string]()
	for f, digest := range req.Adapters {
		g.Go(func() error {
161
			if _, err := createBlob(cmd, client, f, digest, p); err != nil {
Michael Yang's avatar
Michael Yang committed
162
163
				return err
			}
164
165
166
167
168

			// TODO: same here
			adapters.Store(filepath.Base(f), digest)
			return nil
		})
Michael Yang's avatar
Michael Yang committed
169
	}
Michael Yang's avatar
Michael Yang committed
170

171
172
173
174
175
176
177
	if err := g.Wait(); err != nil {
		return err
	}

	req.Files = files.Items()
	req.Adapters = adapters.Items()

Michael Yang's avatar
Michael Yang committed
178
	bars := make(map[string]*progress.Bar)
179
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
180
181
182
		if resp.Digest != "" {
			bar, ok := bars[resp.Digest]
			if !ok {
183
184
185
186
187
				msg := resp.Status
				if msg == "" {
					msg = fmt.Sprintf("pulling %s...", resp.Digest[7:19])
				}
				bar = progress.NewBar(msg, resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
188
189
190
191
192
193
194
195
196
197
198
199
200
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
			spinner.Stop()

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

201
202
203
		return nil
	}

204
	if err := client.Create(cmd.Context(), req, fn); err != nil {
205
206
207
		if strings.Contains(err.Error(), "path or Modelfile are required") {
			return fmt.Errorf("the ollama server must be updated to use `ollama create` with this client")
		}
208
209
210
211
212
213
		return err
	}

	return nil
}

214
215
func createBlob(cmd *cobra.Command, client *api.Client, path string, digest string, p *progress.Progress) (string, error) {
	realPath, err := filepath.EvalSymlinks(path)
Michael Yang's avatar
Michael Yang committed
216
217
218
219
	if err != nil {
		return "", err
	}

220
	bin, err := os.Open(realPath)
221
222
223
224
225
	if err != nil {
		return "", err
	}
	defer bin.Close()

226
227
228
229
230
231
232
233
	// Get file info to retrieve the size
	fileInfo, err := bin.Stat()
	if err != nil {
		return "", err
	}
	fileSize := fileInfo.Size()

	var pw progressWriter
234
235
236
237
	status := fmt.Sprintf("copying file %s 0%%", digest)
	spinner := progress.NewSpinner(status)
	p.Add(status, spinner)
	defer spinner.Stop()
238
239
240
241
242
243
244
245
246
247

	done := make(chan struct{})
	defer close(done)

	go func() {
		ticker := time.NewTicker(60 * time.Millisecond)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
248
				spinner.SetMessage(fmt.Sprintf("copying file %s %d%%", digest, int(100*pw.n.Load()/fileSize)))
249
			case <-done:
250
				spinner.SetMessage(fmt.Sprintf("copying file %s 100%%", digest))
251
252
253
254
255
				return
			}
		}
	}()

256
	if err := client.CreateBlob(cmd.Context(), digest, io.TeeReader(bin, &pw)); err != nil {
257
258
259
260
261
		return "", err
	}
	return digest, nil
}

262
263
264
265
266
267
268
269
270
type progressWriter struct {
	n atomic.Int64
}

func (w *progressWriter) Write(p []byte) (n int, err error) {
	w.n.Add(int64(len(p)))
	return len(p), nil
}

Patrick Devine's avatar
Patrick Devine committed
271
272
273
274
275
276
277
278
279
280
281
282
func loadOrUnloadModel(cmd *cobra.Command, opts *runOptions) error {
	p := progress.NewProgress(os.Stderr)
	defer p.StopAndClear()

	spinner := progress.NewSpinner("")
	p.Add("", spinner)

	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

283
284
285
286
287
288
289
	if info, err := client.Show(cmd.Context(), &api.ShowRequest{Model: opts.Model}); err != nil {
		return err
	} else if info.RemoteHost != "" {
		// Cloud model, no need to load/unload
		return nil
	}

Patrick Devine's avatar
Patrick Devine committed
290
291
292
	req := &api.GenerateRequest{
		Model:     opts.Model,
		KeepAlive: opts.KeepAlive,
293
294
295

		// pass Think here so we fail before getting to the chat prompt if the model doesn't support it
		Think: opts.Think,
Patrick Devine's avatar
Patrick Devine committed
296
297
	}

298
299
300
301
302
303
304
305
306
307
308
	return client.Generate(cmd.Context(), req, func(r api.GenerateResponse) error {
		if r.RemoteModel != "" && opts.ShowConnect {
			p.StopAndClear()
			if strings.HasPrefix(r.RemoteHost, "https://ollama.com") {
				fmt.Fprintf(os.Stderr, "Connecting to '%s' on 'ollama.com' ⚡\n", r.RemoteModel)
			} else {
				fmt.Fprintf(os.Stderr, "Connecting to '%s' on '%s'\n", r.RemoteModel, r.RemoteHost)
			}
		}
		return nil
	})
Patrick Devine's avatar
Patrick Devine committed
309
310
311
312
313
314
315
316
317
318
319
}

func StopHandler(cmd *cobra.Command, args []string) error {
	opts := &runOptions{
		Model:     args[0],
		KeepAlive: &api.Duration{Duration: 0},
	}
	if err := loadOrUnloadModel(cmd, opts); err != nil {
		if strings.Contains(err.Error(), "not found") {
			return fmt.Errorf("couldn't find model \"%s\" to stop", args[0])
		}
320
		return err
Patrick Devine's avatar
Patrick Devine committed
321
322
323
324
	}
	return nil
}

325
func RunHandler(cmd *cobra.Command, args []string) error {
326
327
328
	interactive := true

	opts := runOptions{
329
330
331
332
		Model:       args[0],
		WordWrap:    os.Getenv("TERM") == "xterm-256color",
		Options:     map[string]any{},
		ShowConnect: true,
333
334
335
336
337
338
339
340
	}

	format, err := cmd.Flags().GetString("format")
	if err != nil {
		return err
	}
	opts.Format = format

341
342
	thinkFlag := cmd.Flags().Lookup("think")
	if thinkFlag.Changed {
Michael Yang's avatar
Michael Yang committed
343
		thinkStr, err := cmd.Flags().GetString("think")
344
345
346
		if err != nil {
			return err
		}
Michael Yang's avatar
Michael Yang committed
347
348
349
350
351
352
353
354
355
356
357
358
359

		// Handle different values for --think
		switch thinkStr {
		case "", "true":
			// --think or --think=true
			opts.Think = &api.ThinkValue{Value: true}
		case "false":
			opts.Think = &api.ThinkValue{Value: false}
		case "high", "medium", "low":
			opts.Think = &api.ThinkValue{Value: thinkStr}
		default:
			return fmt.Errorf("invalid value for --think: %q (must be true, false, high, medium, or low)", thinkStr)
		}
360
361
362
363
364
365
366
367
368
	} else {
		opts.Think = nil
	}
	hidethinking, err := cmd.Flags().GetBool("hidethinking")
	if err != nil {
		return err
	}
	opts.HideThinking = hidethinking

369
370
371
372
373
374
375
376
377
378
379
380
	keepAlive, err := cmd.Flags().GetString("keepalive")
	if err != nil {
		return err
	}
	if keepAlive != "" {
		d, err := time.ParseDuration(keepAlive)
		if err != nil {
			return err
		}
		opts.KeepAlive = &api.Duration{Duration: d}
	}

381
382
383
384
385
386
387
388
389
	prompts := args[1:]
	// prepend stdin to the prompt if provided
	if !term.IsTerminal(int(os.Stdin.Fd())) {
		in, err := io.ReadAll(os.Stdin)
		if err != nil {
			return err
		}

		prompts = append([]string{string(in)}, prompts...)
390
		opts.ShowConnect = false
391
392
393
394
395
396
397
		opts.WordWrap = false
		interactive = false
	}
	opts.Prompt = strings.Join(prompts, " ")
	if len(prompts) > 0 {
		interactive = false
	}
398
399
400
401
	// Be quiet if we're redirecting to a pipe or file
	if !term.IsTerminal(int(os.Stdout.Fd())) {
		interactive = false
	}
402
403
404
405
406
407
408

	nowrap, err := cmd.Flags().GetBool("nowordwrap")
	if err != nil {
		return err
	}
	opts.WordWrap = !nowrap

409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
	// Fill out the rest of the options based on information about the
	// model.
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

	name := args[0]
	info, err := func() (*api.ShowResponse, error) {
		showReq := &api.ShowRequest{Name: name}
		info, err := client.Show(cmd.Context(), showReq)
		var se api.StatusError
		if errors.As(err, &se) && se.StatusCode == http.StatusNotFound {
			if err := PullHandler(cmd, []string{name}); err != nil {
				return nil, err
			}
			return client.Show(cmd.Context(), &api.ShowRequest{Name: name})
		}
		return info, err
	}()
	if err != nil {
		return err
431
432
	}

433
434
435
436
437
	opts.Think, err = inferThinkingOption(&info.Capabilities, &opts, thinkFlag.Changed)
	if err != nil {
		return err
	}

438
439
440
441
442
	opts.MultiModal = slices.Contains(info.Capabilities, model.CapabilityVision)

	// TODO: remove the projector info and vision info checks below,
	// these are left in for backwards compatibility with older servers
	// that don't have the capabilities field in the model info
443
444
445
446
447
448
449
450
451
452
	if len(info.ProjectorInfo) != 0 {
		opts.MultiModal = true
	}
	for k := range info.ModelInfo {
		if strings.Contains(k, ".vision.") {
			opts.MultiModal = true
			break
		}
	}

453
454
455
	opts.ParentModel = info.Details.ParentModel

	if interactive {
Patrick Devine's avatar
Patrick Devine committed
456
		if err := loadOrUnloadModel(cmd, &opts); err != nil {
457
458
			var sErr api.AuthorizationError
			if errors.As(err, &sErr) && sErr.StatusCode == http.StatusUnauthorized {
459
460
461
462
				fmt.Printf("You need to be signed in to Ollama to run Cloud models.\n\n")

				if sErr.SigninURL != "" {
					fmt.Printf(ConnectInstructions, sErr.SigninURL)
463
464
465
				}
				return nil
			}
Michael Yang's avatar
Michael Yang committed
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
			return err
		}

		for _, msg := range info.Messages {
			switch msg.Role {
			case "user":
				fmt.Printf(">>> %s\n", msg.Content)
			case "assistant":
				state := &displayResponseState{}
				displayResponse(msg.Content, opts.WordWrap, state)
				fmt.Println()
				fmt.Println()
			}
		}

481
482
483
		return generateInteractive(cmd, opts)
	}
	return generate(cmd, opts)
Bruce MacDonald's avatar
Bruce MacDonald committed
484
485
}

486
487
488
489
490
491
492
493
func SigninHandler(cmd *cobra.Command, args []string) error {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

	user, err := client.Whoami(cmd.Context())
	if err != nil {
494
495
496
497
498
499
500
501
502
503
		var aErr api.AuthorizationError
		if errors.As(err, &aErr) && aErr.StatusCode == http.StatusUnauthorized {
			fmt.Println("You need to be signed in to Ollama to run Cloud models.")
			fmt.Println()

			if aErr.SigninURL != "" {
				fmt.Printf(ConnectInstructions, aErr.SigninURL)
			}
			return nil
		}
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
		return err
	}

	if user != nil && user.Name != "" {
		fmt.Printf("You are already signed in as user '%s'\n", user.Name)
		fmt.Println()
		return nil
	}

	return nil
}

func SignoutHandler(cmd *cobra.Command, args []string) error {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

522
	err = client.Signout(cmd.Context())
523
	if err != nil {
524
525
526
527
528
529
530
531
		var aErr api.AuthorizationError
		if errors.As(err, &aErr) && aErr.StatusCode == http.StatusUnauthorized {
			fmt.Println("You are not signed in to ollama.com")
			fmt.Println()
			return nil
		} else {
			return err
		}
532
	}
533

534
535
536
537
538
	fmt.Println("You have signed out of ollama.com")
	fmt.Println()
	return nil
}

539
func PushHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
540
	client, err := api.ClientFromEnvironment()
541
542
543
	if err != nil {
		return err
	}
544

545
546
547
548
549
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
	n := model.ParseName(args[0])
	if strings.HasSuffix(n.Host, ".ollama.ai") || strings.HasSuffix(n.Host, ".ollama.com") {
		_, err := client.Whoami(cmd.Context())
		if err != nil {
			var aErr api.AuthorizationError
			if errors.As(err, &aErr) && aErr.StatusCode == http.StatusUnauthorized {
				fmt.Println("You need to be signed in to push models to ollama.com.")
				fmt.Println()

				if aErr.SigninURL != "" {
					fmt.Printf(ConnectInstructions, aErr.SigninURL)
				}
				return nil
			}

			return err
		}
	}

Michael Yang's avatar
Michael Yang committed
569
570
571
572
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

	bars := make(map[string]*progress.Bar)
573
574
	var status string
	var spinner *progress.Spinner
Michael Yang's avatar
Michael Yang committed
575

576
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
577
		if resp.Digest != "" {
578
579
580
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
581
582
583

			bar, ok := bars[resp.Digest]
			if !ok {
584
				bar = progress.NewBar(fmt.Sprintf("pushing %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
585
586
587
588
589
590
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
591
592
593
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
594
595
596
597
598
599

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

600
601
602
		return nil
	}

Michael Yang's avatar
Michael Yang committed
603
	request := api.PushRequest{Name: args[0], Insecure: insecure}
604

Michael Yang's avatar
Michael Yang committed
605
	if err := client.Push(cmd.Context(), &request, fn); err != nil {
606
607
608
		if spinner != nil {
			spinner.Stop()
		}
609
610
		errStr := strings.ToLower(err.Error())
		if strings.Contains(errStr, "access denied") || strings.Contains(errStr, "unauthorized") {
611
612
			return errors.New("you are not authorized to push to this namespace, create the model under a namespace you own")
		}
Michael Yang's avatar
Michael Yang committed
613
614
615
		return err
	}

616
	p.Stop()
617
	spinner.Stop()
618
619
620
621
622
623
624
625

	destination := n.String()
	if strings.HasSuffix(n.Host, ".ollama.ai") || strings.HasSuffix(n.Host, ".ollama.com") {
		destination = "https://ollama.com/" + strings.TrimSuffix(n.DisplayShortest(), ":latest")
	}
	fmt.Printf("\nYou can find your model at:\n\n")
	fmt.Printf("\t%s\n", destination)

Michael Yang's avatar
Michael Yang committed
626
	return nil
627
628
}

629
func ListHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
630
	client, err := api.ClientFromEnvironment()
631
632
633
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
634

Michael Yang's avatar
Michael Yang committed
635
	models, err := client.List(cmd.Context())
Patrick Devine's avatar
Patrick Devine committed
636
637
638
639
640
641
642
	if err != nil {
		return err
	}

	var data [][]string

	for _, m := range models.Models {
643
		if len(args) == 0 || strings.HasPrefix(strings.ToLower(m.Name), strings.ToLower(args[0])) {
644
645
646
647
648
649
650
651
			var size string
			if m.RemoteModel != "" {
				size = "-"
			} else {
				size = format.HumanBytes(m.Size)
			}

			data = append(data, []string{m.Name, m.Digest[:12], size, format.HumanTime(m.ModifiedAt, "Never")})
Michael Yang's avatar
Michael Yang committed
652
		}
Patrick Devine's avatar
Patrick Devine committed
653
654
655
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
656
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
657
658
659
660
661
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetHeaderLine(false)
	table.SetBorder(false)
	table.SetNoWhiteSpace(true)
Michael Yang's avatar
Michael Yang committed
662
	table.SetTablePadding("    ")
Patrick Devine's avatar
Patrick Devine committed
663
664
665
666
667
668
	table.AppendBulk(data)
	table.Render()

	return nil
}

669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
func ListRunningHandler(cmd *cobra.Command, args []string) error {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

	models, err := client.ListRunning(cmd.Context())
	if err != nil {
		return err
	}

	var data [][]string

	for _, m := range models.Models {
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
			var procStr string
			switch {
			case m.SizeVRAM == 0:
				procStr = "100% CPU"
			case m.SizeVRAM == m.Size:
				procStr = "100% GPU"
			case m.SizeVRAM > m.Size || m.Size == 0:
				procStr = "Unknown"
			default:
				sizeCPU := m.Size - m.SizeVRAM
				cpuPercent := math.Round(float64(sizeCPU) / float64(m.Size) * 100)
				procStr = fmt.Sprintf("%d%%/%d%% CPU/GPU", int(cpuPercent), int(100-cpuPercent))
			}
Patrick Devine's avatar
Patrick Devine committed
697
698
699
700
701
702
703
704

			var until string
			delta := time.Since(m.ExpiresAt)
			if delta > 0 {
				until = "Stopping..."
			} else {
				until = format.HumanTime(m.ExpiresAt, "Never")
			}
705
706
			ctxStr := strconv.Itoa(m.ContextLength)
			data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), procStr, ctxStr, until})
707
708
709
710
		}
	}

	table := tablewriter.NewWriter(os.Stdout)
711
	table.SetHeader([]string{"NAME", "ID", "SIZE", "PROCESSOR", "CONTEXT", "UNTIL"})
712
713
714
715
716
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetHeaderLine(false)
	table.SetBorder(false)
	table.SetNoWhiteSpace(true)
Michael Yang's avatar
Michael Yang committed
717
	table.SetTablePadding("    ")
718
719
720
721
722
723
	table.AppendBulk(data)
	table.Render()

	return nil
}

724
func DeleteHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
725
	client, err := api.ClientFromEnvironment()
726
727
728
	if err != nil {
		return err
	}
729

730
731
732
733
734
735
736
737
738
	for _, arg := range args {
		// Unload the model if it's running before deletion
		if err := loadOrUnloadModel(cmd, &runOptions{
			Model:     args[0],
			KeepAlive: &api.Duration{Duration: 0},
		}); err != nil {
			if !strings.Contains(strings.ToLower(err.Error()), "not found") {
				fmt.Fprintf(os.Stderr, "Warning: unable to stop model '%s'\n", args[0])
			}
739
740
		}

741
		if err := client.Delete(cmd.Context(), &api.DeleteRequest{Name: arg}); err != nil {
742
743
			return err
		}
744
		fmt.Printf("deleted '%s'\n", arg)
745
746
747
748
	}
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
749
func ShowHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
750
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
751
752
753
754
755
756
757
758
759
	if err != nil {
		return err
	}

	license, errLicense := cmd.Flags().GetBool("license")
	modelfile, errModelfile := cmd.Flags().GetBool("modelfile")
	parameters, errParams := cmd.Flags().GetBool("parameters")
	system, errSystem := cmd.Flags().GetBool("system")
	template, errTemplate := cmd.Flags().GetBool("template")
760
	verbose, errVerbose := cmd.Flags().GetBool("verbose")
Patrick Devine's avatar
Patrick Devine committed
761

762
	for _, boolErr := range []error{errLicense, errModelfile, errParams, errSystem, errTemplate, errVerbose} {
Patrick Devine's avatar
Patrick Devine committed
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
		if boolErr != nil {
			return errors.New("error retrieving flags")
		}
	}

	flagsSet := 0
	showType := ""

	if license {
		flagsSet++
		showType = "license"
	}

	if modelfile {
		flagsSet++
		showType = "modelfile"
	}

	if parameters {
		flagsSet++
		showType = "parameters"
	}

	if system {
		flagsSet++
		showType = "system"
	}

	if template {
		flagsSet++
		showType = "template"
	}

	if flagsSet > 1 {
797
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
798
799
	}

800
	req := api.ShowRequest{Name: args[0], Verbose: verbose}
801
802
803
804
	resp, err := client.Show(cmd.Context(), &req)
	if err != nil {
		return err
	}
805

806
	if flagsSet == 1 {
807
808
809
810
811
812
813
814
		switch showType {
		case "license":
			fmt.Println(resp.License)
		case "modelfile":
			fmt.Println(resp.Modelfile)
		case "parameters":
			fmt.Println(resp.Parameters)
		case "system":
815
			fmt.Print(resp.System)
816
		case "template":
817
			fmt.Print(resp.Template)
818
819
820
		}

		return nil
Patrick Devine's avatar
Patrick Devine committed
821
822
	}

823
	return showInfo(resp, verbose, os.Stdout)
824
825
}

826
func showInfo(resp *api.ShowResponse, verbose bool, w io.Writer) error {
Michael Yang's avatar
Michael Yang committed
827
828
829
830
831
832
833
	tableRender := func(header string, rows func() [][]string) {
		fmt.Fprintln(w, " ", header)
		table := tablewriter.NewWriter(w)
		table.SetAlignment(tablewriter.ALIGN_LEFT)
		table.SetBorder(false)
		table.SetNoWhiteSpace(true)
		table.SetTablePadding("    ")
834

Michael Yang's avatar
Michael Yang committed
835
836
837
		switch header {
		case "Template", "System", "License":
			table.SetColWidth(100)
838
839
		}

Michael Yang's avatar
Michael Yang committed
840
841
842
		table.AppendBulk(rows())
		table.Render()
		fmt.Fprintln(w)
Patrick Devine's avatar
Patrick Devine committed
843
844
	}

Michael Yang's avatar
Michael Yang committed
845
	tableRender("Model", func() (rows [][]string) {
846
847
848
849
850
		if resp.RemoteHost != "" {
			rows = append(rows, []string{"", "Remote model", resp.RemoteModel})
			rows = append(rows, []string{"", "Remote URL", resp.RemoteHost})
		}

Michael Yang's avatar
Michael Yang committed
851
852
853
		if resp.ModelInfo != nil {
			arch := resp.ModelInfo["general.architecture"].(string)
			rows = append(rows, []string{"", "architecture", arch})
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875

			var paramStr string
			if resp.Details.ParameterSize != "" {
				paramStr = resp.Details.ParameterSize
			} else if v, ok := resp.ModelInfo["general.parameter_count"]; ok {
				if f, ok := v.(float64); ok {
					paramStr = format.HumanNumber(uint64(f))
				}
			}
			rows = append(rows, []string{"", "parameters", paramStr})

			if v, ok := resp.ModelInfo[fmt.Sprintf("%s.context_length", arch)]; ok {
				if f, ok := v.(float64); ok {
					rows = append(rows, []string{"", "context length", strconv.FormatFloat(f, 'f', -1, 64)})
				}
			}

			if v, ok := resp.ModelInfo[fmt.Sprintf("%s.embedding_length", arch)]; ok {
				if f, ok := v.(float64); ok {
					rows = append(rows, []string{"", "embedding length", strconv.FormatFloat(f, 'f', -1, 64)})
				}
			}
Michael Yang's avatar
Michael Yang committed
876
877
878
879
880
881
882
		} else {
			rows = append(rows, []string{"", "architecture", resp.Details.Family})
			rows = append(rows, []string{"", "parameters", resp.Details.ParameterSize})
		}
		rows = append(rows, []string{"", "quantization", resp.Details.QuantizationLevel})
		return
	})
883

884
885
886
887
888
889
890
891
892
	if len(resp.Capabilities) > 0 {
		tableRender("Capabilities", func() (rows [][]string) {
			for _, capability := range resp.Capabilities {
				rows = append(rows, []string{"", capability.String()})
			}
			return
		})
	}

Michael Yang's avatar
Michael Yang committed
893
894
895
896
897
898
899
900
901
	if resp.ProjectorInfo != nil {
		tableRender("Projector", func() (rows [][]string) {
			arch := resp.ProjectorInfo["general.architecture"].(string)
			rows = append(rows, []string{"", "architecture", arch})
			rows = append(rows, []string{"", "parameters", format.HumanNumber(uint64(resp.ProjectorInfo["general.parameter_count"].(float64)))})
			rows = append(rows, []string{"", "embedding length", strconv.FormatFloat(resp.ProjectorInfo[fmt.Sprintf("%s.vision.embedding_length", arch)].(float64), 'f', -1, 64)})
			rows = append(rows, []string{"", "dimensions", strconv.FormatFloat(resp.ProjectorInfo[fmt.Sprintf("%s.vision.projection_dim", arch)].(float64), 'f', -1, 64)})
			return
		})
902
903
	}

Michael Yang's avatar
Michael Yang committed
904
905
906
907
908
909
910
911
912
913
	if resp.Parameters != "" {
		tableRender("Parameters", func() (rows [][]string) {
			scanner := bufio.NewScanner(strings.NewReader(resp.Parameters))
			for scanner.Scan() {
				if text := scanner.Text(); text != "" {
					rows = append(rows, append([]string{""}, strings.Fields(text)...))
				}
			}
			return
		})
914
915
	}

916
917
918
919
920
921
922
923
924
925
926
	if resp.ModelInfo != nil && verbose {
		tableRender("Metadata", func() (rows [][]string) {
			keys := make([]string, 0, len(resp.ModelInfo))
			for k := range resp.ModelInfo {
				keys = append(keys, k)
			}
			sort.Strings(keys)

			for _, k := range keys {
				var v string
				switch vData := resp.ModelInfo[k].(type) {
927
928
				case bool:
					v = fmt.Sprintf("%t", vData)
929
930
931
932
933
				case string:
					v = vData
				case float64:
					v = fmt.Sprintf("%g", vData)
				case []any:
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
					targetWidth := 10 // Small width where we are displaying the data in a column

					var itemsToShow int
					totalWidth := 1 // Start with 1 for opening bracket

					// Find how many we can fit
					for i := range vData {
						itemStr := fmt.Sprintf("%v", vData[i])
						width := runewidth.StringWidth(itemStr)

						// Add separator width (", ") for all items except the first
						if i > 0 {
							width += 2
						}

						// Check if adding this item would exceed our width limit
						if totalWidth+width > targetWidth && i > 0 {
							break
						}

						totalWidth += width
						itemsToShow++
					}

					// Format the output
					if itemsToShow < len(vData) {
						v = fmt.Sprintf("%v", vData[:itemsToShow])
						v = strings.TrimSuffix(v, "]")
						v += fmt.Sprintf(" ...+%d more]", len(vData)-itemsToShow)
					} else {
						v = fmt.Sprintf("%v", vData)
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
					}
				default:
					v = fmt.Sprintf("%T", vData)
				}
				rows = append(rows, []string{"", k, v})
			}
			return
		})
	}

	if len(resp.Tensors) > 0 && verbose {
		tableRender("Tensors", func() (rows [][]string) {
			for _, t := range resp.Tensors {
				rows = append(rows, []string{"", t.Name, t.Type, fmt.Sprint(t.Shape)})
			}
			return
		})
	}

Michael Yang's avatar
Michael Yang committed
984
985
	head := func(s string, n int) (rows [][]string) {
		scanner := bufio.NewScanner(strings.NewReader(s))
986
987
988
989
990
		count := 0
		for scanner.Scan() {
			text := strings.TrimSpace(scanner.Text())
			if text == "" {
				continue
991
			}
992
993
994
995
996
997
998
			count++
			if n < 0 || count <= n {
				rows = append(rows, []string{"", text})
			}
		}
		if n >= 0 && count > n {
			rows = append(rows, []string{"", "..."})
999
		}
Michael Yang's avatar
Michael Yang committed
1000
		return
1001
1002
	}

Michael Yang's avatar
Michael Yang committed
1003
1004
1005
1006
1007
	if resp.System != "" {
		tableRender("System", func() [][]string {
			return head(resp.System, 2)
		})
	}
1008

Michael Yang's avatar
Michael Yang committed
1009
1010
1011
1012
	if resp.License != "" {
		tableRender("License", func() [][]string {
			return head(resp.License, 2)
		})
1013
	}
Michael Yang's avatar
Michael Yang committed
1014
1015

	return nil
1016
1017
}

Patrick Devine's avatar
Patrick Devine committed
1018
func CopyHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
1019
	client, err := api.ClientFromEnvironment()
1020
1021
1022
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
1023
1024

	req := api.CopyRequest{Source: args[0], Destination: args[1]}
Michael Yang's avatar
Michael Yang committed
1025
	if err := client.Copy(cmd.Context(), &req); err != nil {
Patrick Devine's avatar
Patrick Devine committed
1026
1027
1028
1029
1030
1031
		return err
	}
	fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
	return nil
}

1032
func PullHandler(cmd *cobra.Command, args []string) error {
1033
1034
1035
1036
1037
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
1038
	client, err := api.ClientFromEnvironment()
1039
1040
1041
	if err != nil {
		return err
	}
1042

Michael Yang's avatar
Michael Yang committed
1043
1044
1045
1046
1047
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

	bars := make(map[string]*progress.Bar)

1048
1049
	var status string
	var spinner *progress.Spinner
Michael Yang's avatar
Michael Yang committed
1050

1051
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
1052
		if resp.Digest != "" {
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
			if resp.Completed == 0 {
				// This is the initial status update for the
				// layer, which the server sends before
				// beginning the download, for clients to
				// compute total size and prepare for
				// downloads, if needed.
				//
				// Skipping this here to avoid showing a 0%
				// progress bar, which *should* clue the user
				// into the fact that many things are being
				// downloaded and that the current active
				// download is not that last. However, in rare
				// cases it seems to be triggering to some, and
				// it isn't worth explaining, so just ignore
				// and regress to the old UI that keeps giving
				// you the "But wait, there is more!" after
				// each "100% done" bar, which is "better."
				return nil
			}

1073
1074
1075
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
1076
1077
1078

			bar, ok := bars[resp.Digest]
			if !ok {
1079
1080
1081
1082
1083
1084
				name, isDigest := strings.CutPrefix(resp.Digest, "sha256:")
				name = strings.TrimSpace(name)
				if isDigest {
					name = name[:min(12, len(name))]
				}
				bar = progress.NewBar(fmt.Sprintf("pulling %s:", name), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
1085
1086
1087
1088
1089
1090
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
1091
1092
1093
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
1094
1095
1096
1097
1098
1099

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

1100
1101
		return nil
	}
1102

Michael Yang's avatar
Michael Yang committed
1103
	request := api.PullRequest{Name: args[0], Insecure: insecure}
1104
	return client.Pull(cmd.Context(), &request, fn)
Michael Yang's avatar
Michael Yang committed
1105
1106
}

1107
1108
type generateContextKey string

1109
type runOptions struct {
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
	Model        string
	ParentModel  string
	Prompt       string
	Messages     []api.Message
	WordWrap     bool
	Format       string
	System       string
	Images       []api.ImageData
	Options      map[string]any
	MultiModal   bool
	KeepAlive    *api.Duration
Michael Yang's avatar
Michael Yang committed
1121
	Think        *api.ThinkValue
1122
	HideThinking bool
1123
	ShowConnect  bool
1124
1125
}

1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
func (r runOptions) Copy() runOptions {
	var messages []api.Message
	if r.Messages != nil {
		messages = make([]api.Message, len(r.Messages))
		copy(messages, r.Messages)
	}

	var images []api.ImageData
	if r.Images != nil {
		images = make([]api.ImageData, len(r.Images))
		copy(images, r.Images)
	}

	var opts map[string]any
	if r.Options != nil {
		opts = make(map[string]any, len(r.Options))
		for k, v := range r.Options {
			opts[k] = v
		}
	}

	var think *api.ThinkValue
	if r.Think != nil {
		cThink := *r.Think
		think = &cThink
	}

	return runOptions{
		Model:        r.Model,
		ParentModel:  r.ParentModel,
		Prompt:       r.Prompt,
		Messages:     messages,
		WordWrap:     r.WordWrap,
		Format:       r.Format,
		System:       r.System,
		Images:       images,
		Options:      opts,
		MultiModal:   r.MultiModal,
		KeepAlive:    r.KeepAlive,
		Think:        think,
		HideThinking: r.HideThinking,
		ShowConnect:  r.ShowConnect,
	}
}

1171
1172
1173
1174
1175
1176
1177
1178
1179
type displayResponseState struct {
	lineLength int
	wordBuffer string
}

func displayResponse(content string, wordWrap bool, state *displayResponseState) {
	termWidth, _, _ := term.GetSize(int(os.Stdout.Fd()))
	if wordWrap && termWidth >= 10 {
		for _, ch := range content {
Josh Yan's avatar
Josh Yan committed
1180
1181
			if state.lineLength+1 > termWidth-5 {
				if runewidth.StringWidth(state.wordBuffer) > termWidth-10 {
1182
1183
1184
1185
1186
1187
1188
					fmt.Printf("%s%c", state.wordBuffer, ch)
					state.wordBuffer = ""
					state.lineLength = 0
					continue
				}

				// backtrack the length of the last word and clear to the end of the line
1189
1190
				a := runewidth.StringWidth(state.wordBuffer)
				if a > 0 {
1191
					fmt.Printf("\x1b[%dD", a)
1192
1193
				}
				fmt.Printf("\x1b[K\n")
1194
				fmt.Printf("%s%c", state.wordBuffer, ch)
1195
1196
1197
				chWidth := runewidth.RuneWidth(ch)

				state.lineLength = runewidth.StringWidth(state.wordBuffer) + chWidth
1198
1199
			} else {
				fmt.Print(string(ch))
1200
1201
1202
1203
				state.lineLength += runewidth.RuneWidth(ch)
				if runewidth.RuneWidth(ch) >= 2 {
					state.wordBuffer = ""
					continue
Josh Yan's avatar
Josh Yan committed
1204
				}
1205
1206

				switch ch {
Michael Yang's avatar
Michael Yang committed
1207
				case ' ', '\t':
1208
					state.wordBuffer = ""
Michael Yang's avatar
Michael Yang committed
1209
				case '\n', '\r':
1210
					state.lineLength = 0
Michael Yang's avatar
Michael Yang committed
1211
					state.wordBuffer = ""
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
				default:
					state.wordBuffer += string(ch)
				}
			}
		}
	} else {
		fmt.Printf("%s%s", state.wordBuffer, content)
		if len(state.wordBuffer) > 0 {
			state.wordBuffer = ""
		}
	}
}

1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
func thinkingOutputOpeningText(plainText bool) string {
	text := "Thinking...\n"

	if plainText {
		return text
	}

	return readline.ColorGrey + readline.ColorBold + text + readline.ColorDefault + readline.ColorGrey
}

func thinkingOutputClosingText(plainText bool) string {
	text := "...done thinking.\n\n"

	if plainText {
		return text
	}

	return readline.ColorGrey + readline.ColorBold + text + readline.ColorDefault
}

1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
func chat(cmd *cobra.Command, opts runOptions) (*api.Message, error) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, err
	}

	p := progress.NewProgress(os.Stderr)
	defer p.StopAndClear()

	spinner := progress.NewSpinner("")
	p.Add("", spinner)

	cancelCtx, cancel := context.WithCancel(cmd.Context())
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT)

	go func() {
		<-sigChan
		cancel()
	}()

	var state *displayResponseState = &displayResponseState{}
Michael Yang's avatar
Michael Yang committed
1269
	var thinkingContent strings.Builder
1270
1271
	var latest api.ChatResponse
	var fullResponse strings.Builder
1272
1273
	var thinkTagOpened bool = false
	var thinkTagClosed bool = false
1274

1275
1276
	role := "assistant"

1277
	fn := func(response api.ChatResponse) error {
1278
1279
1280
		if response.Message.Content != "" || !opts.HideThinking {
			p.StopAndClear()
		}
1281
1282
1283
1284

		latest = response

		role = response.Message.Role
1285
1286
1287
1288
		if response.Message.Thinking != "" && !opts.HideThinking {
			if !thinkTagOpened {
				fmt.Print(thinkingOutputOpeningText(false))
				thinkTagOpened = true
Michael Yang's avatar
Michael Yang committed
1289
				thinkTagClosed = false
1290
			}
Michael Yang's avatar
Michael Yang committed
1291
			thinkingContent.WriteString(response.Message.Thinking)
1292
1293
1294
			displayResponse(response.Message.Thinking, opts.WordWrap, state)
		}

1295
		content := response.Message.Content
Michael Yang's avatar
Michael Yang committed
1296
1297
1298
1299
		if thinkTagOpened && !thinkTagClosed && (content != "" || len(response.Message.ToolCalls) > 0) {
			if !strings.HasSuffix(thinkingContent.String(), "\n") {
				fmt.Println()
			}
1300
			fmt.Print(thinkingOutputClosingText(false))
Michael Yang's avatar
Michael Yang committed
1301
			thinkTagOpened = false
1302
			thinkTagClosed = true
Michael Yang's avatar
Michael Yang committed
1303
			state = &displayResponseState{}
1304
1305
1306
1307
1308
		}
		// purposefully not putting thinking blocks in the response, which would
		// only be needed if we later added tool calling to the cli (they get
		// filtered out anyway since current models don't expect them unless you're
		// about to finish some tool calls)
1309
1310
		fullResponse.WriteString(content)

Michael Yang's avatar
Michael Yang committed
1311
1312
1313
1314
1315
1316
1317
		if response.Message.ToolCalls != nil {
			toolCalls := response.Message.ToolCalls
			if len(toolCalls) > 0 {
				fmt.Print(renderToolCalls(toolCalls, false))
			}
		}

1318
1319
1320
1321
1322
		displayResponse(content, opts.WordWrap, state)

		return nil
	}

1323
1324
1325
1326
	if opts.Format == "json" {
		opts.Format = `"` + opts.Format + `"`
	}

1327
1328
1329
	req := &api.ChatRequest{
		Model:    opts.Model,
		Messages: opts.Messages,
1330
		Format:   json.RawMessage(opts.Format),
1331
		Options:  opts.Options,
1332
		Think:    opts.Think,
1333
1334
	}

1335
1336
1337
1338
	if opts.KeepAlive != nil {
		req.KeepAlive = opts.KeepAlive
	}

1339
1340
1341
1342
	if err := client.Chat(cancelCtx, req, fn); err != nil {
		if errors.Is(err, context.Canceled) {
			return nil, nil
		}
1343
1344
1345
1346
1347
1348
1349
1350

		// this error should ideally be wrapped properly by the client
		if strings.Contains(err.Error(), "upstream error") {
			p.StopAndClear()
			fmt.Println("An error occurred while processing your message. Please try again.")
			fmt.Println()
			return nil, nil
		}
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
		return nil, err
	}

	if len(opts.Messages) > 0 {
		fmt.Println()
		fmt.Println()
	}

	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
		return nil, err
	}

	if verbose {
		latest.Summary()
	}

	return &api.Message{Role: role, Content: fullResponse.String()}, nil
}

func generate(cmd *cobra.Command, opts runOptions) error {
Michael Yang's avatar
Michael Yang committed
1372
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
1373
	if err != nil {
1374
		return err
Patrick Devine's avatar
Patrick Devine committed
1375
	}
Michael Yang's avatar
Michael Yang committed
1376

Michael Yang's avatar
Michael Yang committed
1377
	p := progress.NewProgress(os.Stderr)
1378
	defer p.StopAndClear()
1379

Michael Yang's avatar
Michael Yang committed
1380
1381
1382
	spinner := progress.NewSpinner("")
	p.Add("", spinner)

1383
1384
1385
1386
1387
1388
1389
	var latest api.GenerateResponse

	generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
	if !ok {
		generateContext = []int{}
	}

Michael Yang's avatar
Michael Yang committed
1390
	ctx, cancel := context.WithCancel(cmd.Context())
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT)

	go func() {
		<-sigChan
		cancel()
	}()

1401
	var state *displayResponseState = &displayResponseState{}
Michael Yang's avatar
Michael Yang committed
1402
	var thinkingContent strings.Builder
1403
1404
	var thinkTagOpened bool = false
	var thinkTagClosed bool = false
1405

1406
	plainText := !term.IsTerminal(int(os.Stdout.Fd()))
1407

1408
	fn := func(response api.GenerateResponse) error {
Patrick Devine's avatar
Patrick Devine committed
1409
		latest = response
1410
		content := response.Response
1411

1412
1413
1414
1415
1416
1417
1418
1419
		if response.Response != "" || !opts.HideThinking {
			p.StopAndClear()
		}

		if response.Thinking != "" && !opts.HideThinking {
			if !thinkTagOpened {
				fmt.Print(thinkingOutputOpeningText(plainText))
				thinkTagOpened = true
Michael Yang's avatar
Michael Yang committed
1420
				thinkTagClosed = false
1421
			}
Michael Yang's avatar
Michael Yang committed
1422
			thinkingContent.WriteString(response.Thinking)
1423
1424
1425
			displayResponse(response.Thinking, opts.WordWrap, state)
		}

Michael Yang's avatar
Michael Yang committed
1426
1427
1428
1429
		if thinkTagOpened && !thinkTagClosed && (content != "" || len(response.ToolCalls) > 0) {
			if !strings.HasSuffix(thinkingContent.String(), "\n") {
				fmt.Println()
			}
1430
			fmt.Print(thinkingOutputClosingText(plainText))
Michael Yang's avatar
Michael Yang committed
1431
			thinkTagOpened = false
1432
			thinkTagClosed = true
Michael Yang's avatar
Michael Yang committed
1433
			state = &displayResponseState{}
1434
1435
		}

1436
		displayResponse(content, opts.WordWrap, state)
1437

Michael Yang's avatar
Michael Yang committed
1438
1439
1440
1441
1442
1443
1444
		if response.ToolCalls != nil {
			toolCalls := response.ToolCalls
			if len(toolCalls) > 0 {
				fmt.Print(renderToolCalls(toolCalls, plainText))
			}
		}

Patrick Devine's avatar
Patrick Devine committed
1445
1446
		return nil
	}
1447

1448
1449
1450
1451
1452
1453
1454
	if opts.MultiModal {
		opts.Prompt, opts.Images, err = extractFileData(opts.Prompt)
		if err != nil {
			return err
		}
	}

1455
1456
1457
1458
	if opts.Format == "json" {
		opts.Format = `"` + opts.Format + `"`
	}

Michael Yang's avatar
Michael Yang committed
1459
	request := api.GenerateRequest{
1460
1461
1462
1463
		Model:     opts.Model,
		Prompt:    opts.Prompt,
		Context:   generateContext,
		Images:    opts.Images,
1464
		Format:    json.RawMessage(opts.Format),
1465
1466
1467
		System:    opts.System,
		Options:   opts.Options,
		KeepAlive: opts.KeepAlive,
1468
		Think:     opts.Think,
Michael Yang's avatar
Michael Yang committed
1469
1470
1471
	}

	if err := client.Generate(ctx, &request, fn); err != nil {
1472
		if errors.Is(err, context.Canceled) {
1473
			return nil
1474
		}
1475
		return err
Patrick Devine's avatar
Patrick Devine committed
1476
	}
1477

1478
	if opts.Prompt != "" {
Michael Yang's avatar
Michael Yang committed
1479
1480
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
1481
	}
1482

1483
1484
1485
1486
	if !latest.Done {
		return nil
	}

Patrick Devine's avatar
Patrick Devine committed
1487
1488
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
1489
		return err
Patrick Devine's avatar
Patrick Devine committed
1490
	}
Michael Yang's avatar
Michael Yang committed
1491

Patrick Devine's avatar
Patrick Devine committed
1492
1493
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
1494
	}
Michael Yang's avatar
Michael Yang committed
1495

Patrick Devine's avatar
Patrick Devine committed
1496
1497
1498
	ctx = context.WithValue(cmd.Context(), generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

1499
	return nil
Michael Yang's avatar
Michael Yang committed
1500
1501
}

1502
func RunServer(_ *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
1503
	if err := initializeKeypair(); err != nil {
1504
1505
1506
		return err
	}

Michael Yang's avatar
host  
Michael Yang committed
1507
	ln, err := net.Listen("tcp", envconfig.Host().Host)
1508
1509
1510
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1511

1512
1513
1514
1515
1516
1517
	err = server.Serve(ln)
	if errors.Is(err, http.ErrServerClosed) {
		return nil
	}

	return err
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1518
1519
}

1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
func initializeKeypair() error {
	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

	privKeyPath := filepath.Join(home, ".ollama", "id_ed25519")
	pubKeyPath := filepath.Join(home, ".ollama", "id_ed25519.pub")

	_, err = os.Stat(privKeyPath)
	if os.IsNotExist(err) {
		fmt.Printf("Couldn't find '%s'. Generating new private key.\n", privKeyPath)
Michael Yang's avatar
Michael Yang committed
1532
		cryptoPublicKey, cryptoPrivateKey, err := ed25519.GenerateKey(rand.Reader)
1533
1534
1535
1536
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
1537
		privateKeyBytes, err := ssh.MarshalPrivateKey(cryptoPrivateKey, "")
1538
1539
1540
1541
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
1542
		if err := os.MkdirAll(filepath.Dir(privKeyPath), 0o755); err != nil {
1543
1544
1545
			return fmt.Errorf("could not create directory %w", err)
		}

Michael Yang's avatar
Michael Yang committed
1546
		if err := os.WriteFile(privKeyPath, pem.EncodeToMemory(privateKeyBytes), 0o600); err != nil {
1547
1548
1549
			return err
		}

Michael Yang's avatar
Michael Yang committed
1550
		sshPublicKey, err := ssh.NewPublicKey(cryptoPublicKey)
1551
1552
1553
1554
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
1555
		publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
1556

Michael Yang's avatar
Michael Yang committed
1557
		if err := os.WriteFile(pubKeyPath, publicKeyBytes, 0o644); err != nil {
1558
1559
1560
			return err
		}

Michael Yang's avatar
Michael Yang committed
1561
		fmt.Printf("Your new public key is: \n\n%s\n", publicKeyBytes)
1562
1563
1564
1565
	}
	return nil
}

Michael Yang's avatar
Michael Yang committed
1566
func checkServerHeartbeat(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
1567
	client, err := api.ClientFromEnvironment()
1568
1569
1570
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
1571
	if err := client.Heartbeat(cmd.Context()); err != nil {
1572
		if !(strings.Contains(err.Error(), " refused") || strings.Contains(err.Error(), "could not connect")) {
Bruce MacDonald's avatar
Bruce MacDonald committed
1573
1574
			return err
		}
1575
		if err := startApp(cmd.Context(), client); err != nil {
1576
			return fmt.Errorf("ollama server not responding - %w", err)
1577
1578
1579
1580
1581
		}
	}
	return nil
}

Michael Yang's avatar
Michael Yang committed
1582
1583
1584
1585
1586
1587
1588
1589
func versionHandler(cmd *cobra.Command, _ []string) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return
	}

	serverVersion, err := client.Version(cmd.Context())
	if err != nil {
Michael Yang's avatar
Michael Yang committed
1590
1591
1592
1593
1594
		fmt.Println("Warning: could not connect to a running Ollama instance")
	}

	if serverVersion != "" {
		fmt.Printf("ollama version is %s\n", serverVersion)
Michael Yang's avatar
Michael Yang committed
1595
1596
	}

1597
	if serverVersion != version.Version {
Michael Yang's avatar
Michael Yang committed
1598
		fmt.Printf("Warning: client version is %s\n", version.Version)
1599
	}
Michael Yang's avatar
Michael Yang committed
1600
1601
}

1602
func appendEnvDocs(cmd *cobra.Command, envs []envconfig.EnvVar) {
1603
1604
1605
1606
1607
	if len(envs) == 0 {
		return
	}

	envUsage := `
1608
1609
Environment Variables:
`
1610
	for _, e := range envs {
1611
		envUsage += fmt.Sprintf("      %-24s   %s\n", e.Name, e.Description)
1612
1613
1614
	}

	cmd.SetUsageTemplate(cmd.UsageTemplate() + envUsage)
1615
1616
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1617
1618
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
Michael Yang's avatar
Michael Yang committed
1619
	cobra.EnableCommandSorting = false
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1620

1621
	if runtime.GOOS == "windows" && term.IsTerminal(int(os.Stdout.Fd())) {
1622
		console.ConsoleFromFile(os.Stdin) //nolint:errcheck
1623
1624
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1625
	rootCmd := &cobra.Command{
1626
1627
1628
1629
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1630
1631
1632
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
1633
1634
1635
1636
1637
1638
1639
1640
		Run: func(cmd *cobra.Command, args []string) {
			if version, _ := cmd.Flags().GetBool("version"); version {
				versionHandler(cmd, args)
				return
			}

			cmd.Print(cmd.UsageString())
		},
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1641
1642
	}

Michael Yang's avatar
Michael Yang committed
1643
	rootCmd.Flags().BoolP("version", "v", false, "Show version information")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1644

1645
	createCmd := &cobra.Command{
1646
		Use:     "create MODEL",
1647
		Short:   "Create a model",
Michael Yang's avatar
Michael Yang committed
1648
		Args:    cobra.ExactArgs(1),
1649
1650
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
1651
1652
	}

1653
	createCmd.Flags().StringP("file", "f", "", "Name of the Modelfile (default \"Modelfile\")")
1654
	createCmd.Flags().StringP("quantize", "q", "", "Quantize model to this level (e.g. q4_K_M)")
1655

Patrick Devine's avatar
Patrick Devine committed
1656
1657
1658
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
Michael Yang's avatar
Michael Yang committed
1659
		Args:    cobra.ExactArgs(1),
Patrick Devine's avatar
Patrick Devine committed
1660
1661
1662
1663
1664
1665
1666
1667
		PreRunE: checkServerHeartbeat,
		RunE:    ShowHandler,
	}

	showCmd.Flags().Bool("license", false, "Show license of a model")
	showCmd.Flags().Bool("modelfile", false, "Show Modelfile of a model")
	showCmd.Flags().Bool("parameters", false, "Show parameters of a model")
	showCmd.Flags().Bool("template", false, "Show template of a model")
1668
	showCmd.Flags().Bool("system", false, "Show system message of a model")
1669
	showCmd.Flags().BoolP("verbose", "v", false, "Show detailed model information")
Patrick Devine's avatar
Patrick Devine committed
1670

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1671
	runCmd := &cobra.Command{
1672
1673
1674
1675
1676
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1677
1678
	}

1679
	runCmd.Flags().String("keepalive", "", "Duration to keep a model loaded (e.g. 5m)")
1680
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
1681
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
1682
	runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1683
	runCmd.Flags().String("format", "", "Response format (e.g. json)")
Michael Yang's avatar
Michael Yang committed
1684
1685
	runCmd.Flags().String("think", "", "Enable thinking mode: true/false or high/medium/low for supported models")
	runCmd.Flags().Lookup("think").NoOptDefVal = "true"
1686
	runCmd.Flags().Bool("hidethinking", false, "Hide thinking output (if provided)")
Patrick Devine's avatar
Patrick Devine committed
1687
1688
1689
1690
1691
1692
1693
1694
1695

	stopCmd := &cobra.Command{
		Use:     "stop MODEL",
		Short:   "Stop a running model",
		Args:    cobra.ExactArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    StopHandler,
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1696
1697
1698
1699
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
1700
		Args:    cobra.ExactArgs(0),
Michael Yang's avatar
Michael Yang committed
1701
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1702
1703
	}

1704
	pullCmd := &cobra.Command{
1705
1706
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
Michael Yang's avatar
Michael Yang committed
1707
		Args:    cobra.ExactArgs(1),
1708
1709
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
1710
1711
	}

1712
1713
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

1714
	pushCmd := &cobra.Command{
1715
1716
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
Michael Yang's avatar
Michael Yang committed
1717
		Args:    cobra.ExactArgs(1),
1718
1719
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
1720
1721
	}

1722
1723
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
	signinCmd := &cobra.Command{
		Use:     "signin",
		Short:   "Sign in to ollama.com",
		Args:    cobra.ExactArgs(0),
		PreRunE: checkServerHeartbeat,
		RunE:    SigninHandler,
	}

	signoutCmd := &cobra.Command{
		Use:     "signout",
		Short:   "Sign out from ollama.com",
		Args:    cobra.ExactArgs(0),
		PreRunE: checkServerHeartbeat,
		RunE:    SignoutHandler,
	}

Patrick Devine's avatar
Patrick Devine committed
1740
	listCmd := &cobra.Command{
1741
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
1742
		Aliases: []string{"ls"},
1743
		Short:   "List models",
1744
		PreRunE: checkServerHeartbeat,
1745
		RunE:    ListHandler,
1746
	}
1747
1748
1749
1750
1751
1752
1753

	psCmd := &cobra.Command{
		Use:     "ps",
		Short:   "List running models",
		PreRunE: checkServerHeartbeat,
		RunE:    ListRunningHandler,
	}
Patrick Devine's avatar
Patrick Devine committed
1754
	copyCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
1755
		Use:     "cp SOURCE DESTINATION",
1756
		Short:   "Copy a model",
Michael Yang's avatar
Michael Yang committed
1757
		Args:    cobra.ExactArgs(2),
1758
1759
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
1760
1761
	}

1762
	deleteCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
1763
		Use:     "rm MODEL [MODEL...]",
1764
1765
1766
1767
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
1768
1769
	}

1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
	runnerCmd := &cobra.Command{
		Use:    "runner",
		Hidden: true,
		RunE: func(cmd *cobra.Command, args []string) error {
			return runner.Execute(os.Args[1:])
		},
		FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
	}
	runnerCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
		_ = runner.Execute(args[1:])
	})

1782
1783
1784
	envVars := envconfig.AsMap()

	envs := []envconfig.EnvVar{envVars["OLLAMA_HOST"]}
1785

1786
1787
1788
1789
	for _, cmd := range []*cobra.Command{
		createCmd,
		showCmd,
		runCmd,
Patrick Devine's avatar
Patrick Devine committed
1790
		stopCmd,
1791
1792
1793
		pullCmd,
		pushCmd,
		listCmd,
1794
		psCmd,
1795
1796
		copyCmd,
		deleteCmd,
1797
		serveCmd,
1798
	} {
1799
1800
		switch cmd {
		case runCmd:
1801
1802
1803
1804
1805
			appendEnvDocs(cmd, []envconfig.EnvVar{envVars["OLLAMA_HOST"], envVars["OLLAMA_NOHISTORY"]})
		case serveCmd:
			appendEnvDocs(cmd, []envconfig.EnvVar{
				envVars["OLLAMA_DEBUG"],
				envVars["OLLAMA_HOST"],
1806
				envVars["OLLAMA_CONTEXT_LENGTH"],
1807
1808
1809
1810
1811
1812
1813
				envVars["OLLAMA_KEEP_ALIVE"],
				envVars["OLLAMA_MAX_LOADED_MODELS"],
				envVars["OLLAMA_MAX_QUEUE"],
				envVars["OLLAMA_MODELS"],
				envVars["OLLAMA_NUM_PARALLEL"],
				envVars["OLLAMA_NOPRUNE"],
				envVars["OLLAMA_ORIGINS"],
1814
				envVars["OLLAMA_SCHED_SPREAD"],
1815
				envVars["OLLAMA_FLASH_ATTENTION"],
1816
				envVars["OLLAMA_KV_CACHE_TYPE"],
1817
				envVars["OLLAMA_LLM_LIBRARY"],
1818
				envVars["OLLAMA_GPU_OVERHEAD"],
1819
				envVars["OLLAMA_LOAD_TIMEOUT"],
1820
			})
1821
1822
1823
		default:
			appendEnvDocs(cmd, envs)
		}
1824
1825
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1826
1827
	rootCmd.AddCommand(
		serveCmd,
1828
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
1829
		showCmd,
1830
		runCmd,
Patrick Devine's avatar
Patrick Devine committed
1831
		stopCmd,
1832
1833
		pullCmd,
		pushCmd,
1834
1835
		signinCmd,
		signoutCmd,
Patrick Devine's avatar
Patrick Devine committed
1836
		listCmd,
1837
		psCmd,
Patrick Devine's avatar
Patrick Devine committed
1838
		copyCmd,
1839
		deleteCmd,
1840
		runnerCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1841
1842
1843
1844
	)

	return rootCmd
}
1845
1846
1847
1848
1849
1850
1851
1852
1853

// If the user has explicitly set thinking options, either through the CLI or
// through the `/set think` or `set nothink` interactive options, then we
// respect them. Otherwise, we check model capabilities to see if the model
// supports thinking. If the model does support thinking, we enable it.
// Otherwise, we unset the thinking option (which is different than setting it
// to false).
//
// If capabilities are not provided, we fetch them from the server.
Michael Yang's avatar
Michael Yang committed
1854
func inferThinkingOption(caps *[]model.Capability, runOpts *runOptions, explicitlySetByUser bool) (*api.ThinkValue, error) {
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
	if explicitlySetByUser {
		return runOpts.Think, nil
	}

	if caps == nil {
		client, err := api.ClientFromEnvironment()
		if err != nil {
			return nil, err
		}
		ret, err := client.Show(context.Background(), &api.ShowRequest{
			Model: runOpts.Model,
		})
		if err != nil {
			return nil, err
		}
		caps = &ret.Capabilities
	}

	thinkingSupported := false
	for _, cap := range *caps {
		if cap == model.CapabilityThinking {
			thinkingSupported = true
		}
	}

	if thinkingSupported {
Michael Yang's avatar
Michael Yang committed
1881
		return &api.ThinkValue{Value: true}, nil
1882
1883
1884
1885
	}

	return nil, nil
}
Michael Yang's avatar
Michael Yang committed
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911

func renderToolCalls(toolCalls []api.ToolCall, plainText bool) string {
	out := ""
	formatExplanation := ""
	formatValues := ""
	if !plainText {
		formatExplanation = readline.ColorGrey + readline.ColorBold
		formatValues = readline.ColorDefault
		out += formatExplanation
	}
	for i, toolCall := range toolCalls {
		argsAsJSON, err := json.Marshal(toolCall.Function.Arguments)
		if err != nil {
			return ""
		}
		if i > 0 {
			out += "\n"
		}
		// all tool calls are unexpected since we don't currently support registering any in the CLI
		out += fmt.Sprintf("  Model called a non-existent function '%s()' with arguments: %s", formatValues+toolCall.Function.Name+formatExplanation, formatValues+string(argsAsJSON)+formatExplanation)
	}
	if !plainText {
		out += readline.ColorDefault
	}
	return out
}